From 5c30107033835792370e19b0a3704eb59b4f5c15 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 16 Jan 2025 05:52:00 +0100 Subject: [PATCH 1/2] cs --- ncs.php | 3 +-- src/PhpGenerator/Dumper.php | 4 ++-- src/PhpGenerator/Helpers.php | 4 ++-- src/PhpGenerator/PhpNamespace.php | 2 +- tests/PhpGenerator/Dumper.dump().phpt | 4 ++-- tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt | 6 +++--- tests/PhpGenerator/PhpFile.phpt | 4 ++-- 7 files changed, 13 insertions(+), 14 deletions(-) diff --git a/ncs.php b/ncs.php index 6dca0c3d..9c743bdd 100644 --- a/ncs.php +++ b/ncs.php @@ -8,7 +8,6 @@ declare(strict_types=1); return [ - // constant NULL, FALSE in src/PhpGenerator/Type.php - 'constant_case' => false, + // constants in src/PhpGenerator/Type.php 'lowercase_static_reference' => false, ]; diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index b6047067..a3aeafc7 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -73,14 +73,14 @@ private function dumpString(string $s): string $utf8 = preg_match('##u', $s); $escaped = preg_replace_callback( - $utf8 ? '#[\p{C}\\\\]#u' : '#[\x00-\x1F\x7F-\xFF\\\\]#', + $utf8 ? '#[\p{C}\\\]#u' : '#[\x00-\x1F\x7F-\xFF\\\]#', fn($m) => $special[$m[0]] ?? (strlen($m[0]) === 1 ? '\x' . str_pad(strtoupper(dechex(ord($m[0]))), 2, '0', STR_PAD_LEFT) : '\u{' . strtoupper(ltrim(dechex(self::utf8Ord($m[0])), '0')) . '}'), $s, ); return $s === str_replace('\\\\', '\\', $escaped) - ? "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $s) . "'" + ? "'" . preg_replace('#\'|\\\(?=[\'\\\]|$)#D', '\\\$0', $s) . "'" : '"' . addcslashes($escaped, '"$') . '"'; } diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 7290d390..fe296991 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -75,7 +75,7 @@ public static function tagName(string $name, string $of = PhpNamespace::NameNorm public static function simplifyTaggedNames(string $code, ?PhpNamespace $namespace): string { - return preg_replace_callback('~/\*\(([ncf])\*/([\w\x7f-\xff\\\\]++)~', function ($m) use ($namespace) { + return preg_replace_callback('~/\*\(([ncf])\*/([\w\x7f-\xff\\\]++)~', function ($m) use ($namespace) { [, $of, $name] = $m; return $namespace ? $namespace->simplifyType($name, $of) @@ -106,7 +106,7 @@ public static function isIdentifier(mixed $value): bool public static function isNamespaceIdentifier(mixed $value, bool $allowLeadingSlash = false): bool { - $re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::ReIdentifier . '(\\\\' . self::ReIdentifier . ')*$#D'; + $re = '#^' . ($allowLeadingSlash ? '\\\?' : '') . self::ReIdentifier . '(\\\\' . self::ReIdentifier . ')*$#D'; return is_string($value) && preg_match($re, $value); } diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index bcd50840..e4d2df62 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -200,7 +200,7 @@ public function resolveName(string $name, string $of = self::NameNormal): string */ public function simplifyType(string $type, string $of = self::NameNormal): string { - return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', fn($m) => $this->simplifyName($m[0], $of), $type); + return preg_replace_callback('~[\w\x7f-\xff\\\]+~', fn($m) => $this->simplifyName($m[0], $of), $type); } diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index 379acbf6..d9469a6e 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -34,8 +34,8 @@ Assert::same("'Hello'", $dumper->dump('Hello')); Assert::same('"\t\n\r\e"', $dumper->dump("\t\n\r\e")); Assert::same('"\u{FEFF}"', $dumper->dump("\xEF\xBB\xBF")); // BOM Assert::same('\'$"\\\\\'', $dumper->dump('$"\\')); -Assert::same('\'$"\\ \x00\'', $dumper->dump('$"\\ \x00')); // no escape -Assert::same('"\\$\\"\\\\ \x00"', $dumper->dump("$\"\\ \x00")); +Assert::same('\'$"\ \x00\'', $dumper->dump('$"\ \x00')); // no escape +Assert::same('"\$\"\\\ \x00"', $dumper->dump("$\"\\ \x00")); Assert::same( "'I\u{F1}t\u{EB}rn\u{E2}ti\u{F4}n\u{E0}liz\u{E6}ti\u{F8}n'", $dumper->dump("I\u{F1}t\u{EB}rn\u{E2}ti\u{F4}n\u{E0}liz\u{E6}ti\u{F8}n"), // Iñtërnâtiônàlizætiøn diff --git a/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt b/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt index acdb92d6..17704c22 100644 --- a/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt +++ b/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt @@ -13,9 +13,9 @@ Assert::true(Helpers::isNamespaceIdentifier("\x7F")); Assert::true(Helpers::isNamespaceIdentifier("\x7F\\\x7F")); Assert::false(Helpers::isNamespaceIdentifier('0Item')); Assert::true(Helpers::isNamespaceIdentifier('Item\Item')); -Assert::false(Helpers::isNamespaceIdentifier('Item\\\\Item')); -Assert::false(Helpers::isNamespaceIdentifier('\\Item')); +Assert::false(Helpers::isNamespaceIdentifier('Item\\\Item')); +Assert::false(Helpers::isNamespaceIdentifier('\Item')); Assert::false(Helpers::isNamespaceIdentifier('Item\\')); -Assert::true(Helpers::isNamespaceIdentifier('\\Item', allowLeadingSlash: true)); +Assert::true(Helpers::isNamespaceIdentifier('\Item', allowLeadingSlash: true)); Assert::false(Helpers::isNamespaceIdentifier('Item\\', allowLeadingSlash: true)); diff --git a/tests/PhpGenerator/PhpFile.phpt b/tests/PhpGenerator/PhpFile.phpt index 04a024d9..f50491c0 100644 --- a/tests/PhpGenerator/PhpFile.phpt +++ b/tests/PhpGenerator/PhpFile.phpt @@ -74,7 +74,7 @@ $interfaceF $traitG = $file->addTrait('Baz\G'); Assert::same($file->addNamespace('Baz'), $traitG->getNamespace()); -$file->addFunction('Baz\\f2') +$file->addFunction('Baz\f2') ->setReturnType('Foo\B'); @@ -112,7 +112,7 @@ Assert::same([ 'FooBar\I', ], array_keys($file->getClasses())); -Assert::same(['Baz\\f2', 'f1'], array_keys($file->getFunctions())); +Assert::same(['Baz\f2', 'f1'], array_keys($file->getFunctions())); From 4cbf229d102fcf8b1a43aa57f53cd3de329f7779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20G=C3=B6r=C3=9F?= Date: Fri, 14 Feb 2025 12:41:07 +0100 Subject: [PATCH 2/2] feat: add wrapping of global function with existing check --- readme.md | 8 +++-- src/PhpGenerator/GlobalFunction.php | 14 ++++++++ src/PhpGenerator/Printer.php | 6 +++- .../PhpGenerator/GlobalFunction.wrapped.phpt | 32 +++++++++++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 tests/PhpGenerator/GlobalFunction.wrapped.phpt diff --git a/readme.md b/readme.md index ae6ef327..ae55bb5f 100644 --- a/readme.md +++ b/readme.md @@ -297,6 +297,7 @@ $function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->setBody('return $a + $b;'); $function->addParameter('a'); $function->addParameter('b'); +$function->wrapInExistingCheck(); echo $function; // or use the PsrPrinter for output compliant with PSR-2 / PSR-12 / PER @@ -306,9 +307,12 @@ echo $function; The result is: ```php -function foo($a, $b) +if (! function_exists('foo')) { - return $a + $b; + function foo($a, $b) + { + return $a + $b; + } } ``` diff --git a/src/PhpGenerator/GlobalFunction.php b/src/PhpGenerator/GlobalFunction.php index c2ed0ac0..c857130f 100644 --- a/src/PhpGenerator/GlobalFunction.php +++ b/src/PhpGenerator/GlobalFunction.php @@ -22,6 +22,8 @@ final class GlobalFunction use Traits\CommentAware; use Traits\AttributeAware; + private bool $wrapInExistingCheck = false; + public static function from(string|\Closure $function, bool $withBody = false): self { return (new Factory)->fromFunctionReflection(Nette\Utils\Callback::toReflection($function), $withBody); @@ -38,4 +40,16 @@ public function __clone(): void { $this->parameters = array_map(fn($param) => clone $param, $this->parameters); } + + + public function wrapInExistingCheck(bool $wrap = true): self + { + $this->wrapInExistingCheck = $wrap; + return $this; + } + + public function getWrapInExistingCheck(): bool + { + return $this->wrapInExistingCheck; + } } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index ab3a9aee..326f22a9 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -49,13 +49,17 @@ public function printFunction(GlobalFunction $function, ?PhpNamespace $namespace $body = $this->printFunctionBody($function); $braceOnNextLine = $this->isBraceOnNextLine(str_contains($params, "\n"), (bool) $returnType); - return $this->printDocComment($function) + $functionPrint = $this->printDocComment($function) . $this->printAttributes($function->getAttributes()) . $line . $params . $returnType . ($braceOnNextLine ? "\n" : ' ') . "{\n" . $this->indent($body) . "}\n"; + + $wrapper = "if (! function_exists('{$function->getName()}'))\n{\n{$this->indent($functionPrint)}}\n"; + + return $function->getWrapInExistingCheck() ? $wrapper : $functionPrint; } diff --git a/tests/PhpGenerator/GlobalFunction.wrapped.phpt b/tests/PhpGenerator/GlobalFunction.wrapped.phpt new file mode 100644 index 00000000..7a70d25e --- /dev/null +++ b/tests/PhpGenerator/GlobalFunction.wrapped.phpt @@ -0,0 +1,32 @@ +setBody('return $a + $b;'); +$function->addAttribute('ExampleAttribute'); +$function->addComment('My Function'); +$function->wrapInExistingCheck(); + +same( + <<<'XX' + if (! function_exists('test')) + { + /** + * My Function + */ + #[ExampleAttribute] + function test() + { + return $a + $b; + } + } + + XX, + (string) $function, +);