PhpNamespace.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (https://nette.org)
  4. * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  5. */
  6. declare(strict_types=1);
  7. namespace Nette\PhpGenerator;
  8. use Nette;
  9. use Nette\InvalidStateException;
  10. /**
  11. * Namespaced part of a PHP file.
  12. *
  13. * Generates:
  14. * - namespace statement
  15. * - variable amount of use statements
  16. * - one or more class declarations
  17. */
  18. final class PhpNamespace
  19. {
  20. use Nette\SmartObject;
  21. public const
  22. NameNormal = 'n',
  23. NameFunction = 'f',
  24. NameConstant = 'c';
  25. public const
  26. NAME_NORMAL = self::NameNormal,
  27. NAME_FUNCTION = self::NameFunction,
  28. NAME_CONSTANT = self::NameConstant;
  29. /** @var string */
  30. private $name;
  31. /** @var bool */
  32. private $bracketedSyntax = false;
  33. /** @var string[][] */
  34. private $aliases = [
  35. self::NameNormal => [],
  36. self::NameFunction => [],
  37. self::NameConstant => [],
  38. ];
  39. /** @var ClassType[] */
  40. private $classes = [];
  41. /** @var GlobalFunction[] */
  42. private $functions = [];
  43. public function __construct(string $name)
  44. {
  45. if ($name !== '' && !Helpers::isNamespaceIdentifier($name)) {
  46. throw new Nette\InvalidArgumentException("Value '$name' is not valid name.");
  47. }
  48. $this->name = $name;
  49. }
  50. public function getName(): string
  51. {
  52. return $this->name;
  53. }
  54. /**
  55. * @return static
  56. * @internal
  57. */
  58. public function setBracketedSyntax(bool $state = true): self
  59. {
  60. $this->bracketedSyntax = $state;
  61. return $this;
  62. }
  63. public function hasBracketedSyntax(): bool
  64. {
  65. return $this->bracketedSyntax;
  66. }
  67. /** @deprecated use hasBracketedSyntax() */
  68. public function getBracketedSyntax(): bool
  69. {
  70. return $this->bracketedSyntax;
  71. }
  72. /**
  73. * @throws InvalidStateException
  74. * @return static
  75. */
  76. public function addUse(string $name, ?string $alias = null, string $of = self::NameNormal): self
  77. {
  78. if (
  79. !Helpers::isNamespaceIdentifier($name, true)
  80. || (Helpers::isIdentifier($name) && isset(Helpers::Keywords[strtolower($name)]))
  81. ) {
  82. throw new Nette\InvalidArgumentException("Value '$name' is not valid class/function/constant name.");
  83. } elseif ($alias && (!Helpers::isIdentifier($alias) || isset(Helpers::Keywords[strtolower($alias)]))) {
  84. throw new Nette\InvalidArgumentException("Value '$alias' is not valid alias.");
  85. }
  86. $name = ltrim($name, '\\');
  87. $aliases = array_change_key_case($this->aliases[$of]);
  88. $used = [self::NameNormal => $this->classes, self::NameFunction => $this->functions, self::NameConstant => []][$of];
  89. if ($alias === null) {
  90. $base = Helpers::extractShortName($name);
  91. $counter = null;
  92. do {
  93. $alias = $base . $counter;
  94. $lower = strtolower($alias);
  95. $counter++;
  96. } while ((isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) || isset($used[$lower]));
  97. } else {
  98. $lower = strtolower($alias);
  99. if (isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) {
  100. throw new InvalidStateException(
  101. "Alias '$alias' used already for '{$aliases[$lower]}', cannot use for '$name'."
  102. );
  103. } elseif (isset($used[$lower])) {
  104. throw new Nette\InvalidStateException("Name '$alias' used already for '$this->name\\{$used[$lower]->getName()}'.");
  105. }
  106. }
  107. $this->aliases[$of][$alias] = $name;
  108. return $this;
  109. }
  110. public function removeUse(string $name, string $of = self::NameNormal): void
  111. {
  112. foreach ($this->aliases[$of] as $alias => $item) {
  113. if (strcasecmp($item, $name) === 0) {
  114. unset($this->aliases[$of][$alias]);
  115. }
  116. }
  117. }
  118. /** @return static */
  119. public function addUseFunction(string $name, ?string $alias = null): self
  120. {
  121. return $this->addUse($name, $alias, self::NameFunction);
  122. }
  123. /** @return static */
  124. public function addUseConstant(string $name, ?string $alias = null): self
  125. {
  126. return $this->addUse($name, $alias, self::NameConstant);
  127. }
  128. /** @return string[] */
  129. public function getUses(string $of = self::NameNormal): array
  130. {
  131. asort($this->aliases[$of]);
  132. return array_filter(
  133. $this->aliases[$of],
  134. function ($name, $alias) { return strcasecmp(($this->name ? $this->name . '\\' : '') . $alias, $name); },
  135. ARRAY_FILTER_USE_BOTH
  136. );
  137. }
  138. /** @deprecated use simplifyName() */
  139. public function unresolveName(string $name): string
  140. {
  141. return $this->simplifyName($name);
  142. }
  143. public function resolveName(string $name, string $of = self::NameNormal): string
  144. {
  145. if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') {
  146. return $name;
  147. } elseif ($name[0] === '\\') {
  148. return substr($name, 1);
  149. }
  150. $aliases = array_change_key_case($this->aliases[$of]);
  151. if ($of !== self::NameNormal) {
  152. return $aliases[strtolower($name)]
  153. ?? $this->resolveName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name);
  154. }
  155. $parts = explode('\\', $name, 2);
  156. return ($res = $aliases[strtolower($parts[0])] ?? null)
  157. ? $res . (isset($parts[1]) ? '\\' . $parts[1] : '')
  158. : $this->name . ($this->name ? '\\' : '') . $name;
  159. }
  160. public function simplifyType(string $type, string $of = self::NameNormal): string
  161. {
  162. return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', function ($m) use ($of) { return $this->simplifyName($m[0], $of); }, $type);
  163. }
  164. public function simplifyName(string $name, string $of = self::NameNormal): string
  165. {
  166. if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') {
  167. return $name;
  168. }
  169. $name = ltrim($name, '\\');
  170. if ($of !== self::NameNormal) {
  171. foreach ($this->aliases[$of] as $alias => $original) {
  172. if (strcasecmp($original, $name) === 0) {
  173. return $alias;
  174. }
  175. }
  176. return $this->simplifyName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name);
  177. }
  178. $shortest = null;
  179. $relative = self::startsWith($name, $this->name . '\\')
  180. ? substr($name, strlen($this->name) + 1)
  181. : null;
  182. foreach ($this->aliases[$of] as $alias => $original) {
  183. if ($relative && self::startsWith($relative . '\\', $alias . '\\')) {
  184. $relative = null;
  185. }
  186. if (self::startsWith($name . '\\', $original . '\\')) {
  187. $short = $alias . substr($name, strlen($original));
  188. if (!isset($shortest) || strlen($shortest) > strlen($short)) {
  189. $shortest = $short;
  190. }
  191. }
  192. }
  193. if (isset($shortest, $relative) && strlen($shortest) < strlen($relative)) {
  194. return $shortest;
  195. }
  196. return $relative ?? $shortest ?? ($this->name ? '\\' : '') . $name;
  197. }
  198. /** @return static */
  199. public function add(ClassType $class): self
  200. {
  201. $name = $class->getName();
  202. if ($name === null) {
  203. throw new Nette\InvalidArgumentException('Class does not have a name.');
  204. }
  205. $lower = strtolower($name);
  206. if ($orig = array_change_key_case($this->aliases[self::NameNormal])[$lower] ?? null) {
  207. throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig.");
  208. }
  209. $this->classes[$lower] = $class;
  210. return $this;
  211. }
  212. public function addClass(string $name): ClassType
  213. {
  214. $this->add($class = new ClassType($name, $this));
  215. return $class;
  216. }
  217. public function addInterface(string $name): ClassType
  218. {
  219. return $this->addClass($name)->setType(ClassType::TYPE_INTERFACE);
  220. }
  221. public function addTrait(string $name): ClassType
  222. {
  223. return $this->addClass($name)->setType(ClassType::TYPE_TRAIT);
  224. }
  225. public function addEnum(string $name): ClassType
  226. {
  227. return $this->addClass($name)->setType(ClassType::TYPE_ENUM);
  228. }
  229. public function removeClass(string $name): self
  230. {
  231. unset($this->classes[strtolower($name)]);
  232. return $this;
  233. }
  234. public function addFunction(string $name): GlobalFunction
  235. {
  236. $lower = strtolower($name);
  237. if ($orig = array_change_key_case($this->aliases[self::NameFunction])[$lower] ?? null) {
  238. throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig.");
  239. }
  240. return $this->functions[$lower] = new GlobalFunction($name);
  241. }
  242. public function removeFunction(string $name): self
  243. {
  244. unset($this->functions[strtolower($name)]);
  245. return $this;
  246. }
  247. /** @return ClassType[] */
  248. public function getClasses(): array
  249. {
  250. $res = [];
  251. foreach ($this->classes as $class) {
  252. $res[$class->getName()] = $class;
  253. }
  254. return $res;
  255. }
  256. /** @return GlobalFunction[] */
  257. public function getFunctions(): array
  258. {
  259. $res = [];
  260. foreach ($this->functions as $fn) {
  261. $res[$fn->getName()] = $fn;
  262. }
  263. return $res;
  264. }
  265. private static function startsWith(string $a, string $b): bool
  266. {
  267. return strncasecmp($a, $b, strlen($b)) === 0;
  268. }
  269. public function __toString(): string
  270. {
  271. try {
  272. return (new Printer)->printNamespace($this);
  273. } catch (\Throwable $e) {
  274. if (PHP_VERSION_ID >= 70400) {
  275. throw $e;
  276. }
  277. trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
  278. return '';
  279. }
  280. }
  281. }