| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- <?php
- /**
- * This file is part of the Nette Framework (https://nette.org)
- * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
- */
- declare(strict_types=1);
- namespace Nette\PhpGenerator;
- use Nette;
- use Nette\InvalidStateException;
- /**
- * Namespaced part of a PHP file.
- *
- * Generates:
- * - namespace statement
- * - variable amount of use statements
- * - one or more class declarations
- */
- final class PhpNamespace
- {
- use Nette\SmartObject;
- public const
- NameNormal = 'n',
- NameFunction = 'f',
- NameConstant = 'c';
- public const
- NAME_NORMAL = self::NameNormal,
- NAME_FUNCTION = self::NameFunction,
- NAME_CONSTANT = self::NameConstant;
- /** @var string */
- private $name;
- /** @var bool */
- private $bracketedSyntax = false;
- /** @var string[][] */
- private $aliases = [
- self::NameNormal => [],
- self::NameFunction => [],
- self::NameConstant => [],
- ];
- /** @var ClassType[] */
- private $classes = [];
- /** @var GlobalFunction[] */
- private $functions = [];
- public function __construct(string $name)
- {
- if ($name !== '' && !Helpers::isNamespaceIdentifier($name)) {
- throw new Nette\InvalidArgumentException("Value '$name' is not valid name.");
- }
- $this->name = $name;
- }
- public function getName(): string
- {
- return $this->name;
- }
- /**
- * @return static
- * @internal
- */
- public function setBracketedSyntax(bool $state = true): self
- {
- $this->bracketedSyntax = $state;
- return $this;
- }
- public function hasBracketedSyntax(): bool
- {
- return $this->bracketedSyntax;
- }
- /** @deprecated use hasBracketedSyntax() */
- public function getBracketedSyntax(): bool
- {
- return $this->bracketedSyntax;
- }
- /**
- * @throws InvalidStateException
- * @return static
- */
- public function addUse(string $name, ?string $alias = null, string $of = self::NameNormal): self
- {
- if (
- !Helpers::isNamespaceIdentifier($name, true)
- || (Helpers::isIdentifier($name) && isset(Helpers::Keywords[strtolower($name)]))
- ) {
- throw new Nette\InvalidArgumentException("Value '$name' is not valid class/function/constant name.");
- } elseif ($alias && (!Helpers::isIdentifier($alias) || isset(Helpers::Keywords[strtolower($alias)]))) {
- throw new Nette\InvalidArgumentException("Value '$alias' is not valid alias.");
- }
- $name = ltrim($name, '\\');
- $aliases = array_change_key_case($this->aliases[$of]);
- $used = [self::NameNormal => $this->classes, self::NameFunction => $this->functions, self::NameConstant => []][$of];
- if ($alias === null) {
- $base = Helpers::extractShortName($name);
- $counter = null;
- do {
- $alias = $base . $counter;
- $lower = strtolower($alias);
- $counter++;
- } while ((isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) || isset($used[$lower]));
- } else {
- $lower = strtolower($alias);
- if (isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) {
- throw new InvalidStateException(
- "Alias '$alias' used already for '{$aliases[$lower]}', cannot use for '$name'."
- );
- } elseif (isset($used[$lower])) {
- throw new Nette\InvalidStateException("Name '$alias' used already for '$this->name\\{$used[$lower]->getName()}'.");
- }
- }
- $this->aliases[$of][$alias] = $name;
- return $this;
- }
- public function removeUse(string $name, string $of = self::NameNormal): void
- {
- foreach ($this->aliases[$of] as $alias => $item) {
- if (strcasecmp($item, $name) === 0) {
- unset($this->aliases[$of][$alias]);
- }
- }
- }
- /** @return static */
- public function addUseFunction(string $name, ?string $alias = null): self
- {
- return $this->addUse($name, $alias, self::NameFunction);
- }
- /** @return static */
- public function addUseConstant(string $name, ?string $alias = null): self
- {
- return $this->addUse($name, $alias, self::NameConstant);
- }
- /** @return string[] */
- public function getUses(string $of = self::NameNormal): array
- {
- asort($this->aliases[$of]);
- return array_filter(
- $this->aliases[$of],
- function ($name, $alias) { return strcasecmp(($this->name ? $this->name . '\\' : '') . $alias, $name); },
- ARRAY_FILTER_USE_BOTH
- );
- }
- /** @deprecated use simplifyName() */
- public function unresolveName(string $name): string
- {
- return $this->simplifyName($name);
- }
- public function resolveName(string $name, string $of = self::NameNormal): string
- {
- if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') {
- return $name;
- } elseif ($name[0] === '\\') {
- return substr($name, 1);
- }
- $aliases = array_change_key_case($this->aliases[$of]);
- if ($of !== self::NameNormal) {
- return $aliases[strtolower($name)]
- ?? $this->resolveName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name);
- }
- $parts = explode('\\', $name, 2);
- return ($res = $aliases[strtolower($parts[0])] ?? null)
- ? $res . (isset($parts[1]) ? '\\' . $parts[1] : '')
- : $this->name . ($this->name ? '\\' : '') . $name;
- }
- public function simplifyType(string $type, string $of = self::NameNormal): string
- {
- return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', function ($m) use ($of) { return $this->simplifyName($m[0], $of); }, $type);
- }
- public function simplifyName(string $name, string $of = self::NameNormal): string
- {
- if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') {
- return $name;
- }
- $name = ltrim($name, '\\');
- if ($of !== self::NameNormal) {
- foreach ($this->aliases[$of] as $alias => $original) {
- if (strcasecmp($original, $name) === 0) {
- return $alias;
- }
- }
- return $this->simplifyName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name);
- }
- $shortest = null;
- $relative = self::startsWith($name, $this->name . '\\')
- ? substr($name, strlen($this->name) + 1)
- : null;
- foreach ($this->aliases[$of] as $alias => $original) {
- if ($relative && self::startsWith($relative . '\\', $alias . '\\')) {
- $relative = null;
- }
- if (self::startsWith($name . '\\', $original . '\\')) {
- $short = $alias . substr($name, strlen($original));
- if (!isset($shortest) || strlen($shortest) > strlen($short)) {
- $shortest = $short;
- }
- }
- }
- if (isset($shortest, $relative) && strlen($shortest) < strlen($relative)) {
- return $shortest;
- }
- return $relative ?? $shortest ?? ($this->name ? '\\' : '') . $name;
- }
- /** @return static */
- public function add(ClassType $class): self
- {
- $name = $class->getName();
- if ($name === null) {
- throw new Nette\InvalidArgumentException('Class does not have a name.');
- }
- $lower = strtolower($name);
- if ($orig = array_change_key_case($this->aliases[self::NameNormal])[$lower] ?? null) {
- throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig.");
- }
- $this->classes[$lower] = $class;
- return $this;
- }
- public function addClass(string $name): ClassType
- {
- $this->add($class = new ClassType($name, $this));
- return $class;
- }
- public function addInterface(string $name): ClassType
- {
- return $this->addClass($name)->setType(ClassType::TYPE_INTERFACE);
- }
- public function addTrait(string $name): ClassType
- {
- return $this->addClass($name)->setType(ClassType::TYPE_TRAIT);
- }
- public function addEnum(string $name): ClassType
- {
- return $this->addClass($name)->setType(ClassType::TYPE_ENUM);
- }
- public function removeClass(string $name): self
- {
- unset($this->classes[strtolower($name)]);
- return $this;
- }
- public function addFunction(string $name): GlobalFunction
- {
- $lower = strtolower($name);
- if ($orig = array_change_key_case($this->aliases[self::NameFunction])[$lower] ?? null) {
- throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig.");
- }
- return $this->functions[$lower] = new GlobalFunction($name);
- }
- public function removeFunction(string $name): self
- {
- unset($this->functions[strtolower($name)]);
- return $this;
- }
- /** @return ClassType[] */
- public function getClasses(): array
- {
- $res = [];
- foreach ($this->classes as $class) {
- $res[$class->getName()] = $class;
- }
- return $res;
- }
- /** @return GlobalFunction[] */
- public function getFunctions(): array
- {
- $res = [];
- foreach ($this->functions as $fn) {
- $res[$fn->getName()] = $fn;
- }
- return $res;
- }
- private static function startsWith(string $a, string $b): bool
- {
- return strncasecmp($a, $b, strlen($b)) === 0;
- }
- public function __toString(): string
- {
- try {
- return (new Printer)->printNamespace($this);
- } catch (\Throwable $e) {
- if (PHP_VERSION_ID >= 70400) {
- throw $e;
- }
- trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
- return '';
- }
- }
- }
|