Fixed xf-dev:entity-class-properties generates invalid output

DragonByte Tech

Well-known member
Affected version
2.2.7 PL1
This function has entirely broken in XF 2.2.7.

In PHP 7.4 it generates the following output:
  • ErrorException: [E_DEPRECATED] Function ReflectionType::__toString() is deprecated
  • src/XF/Cli/Command/Development/EntityClassProperties.php:138

#0 src/XF/Cli/Command/Development/EntityClassProperties.php(138): XF::handlePhpError(8192, '[E_DEPRECATED] ...', '/var/www/html/d...', 138, Array)
#1 src/vendor/symfony/console/Command/Command.php(255): XF\Cli\Command\Development\EntityClassProperties->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#2 src/XF/Cli/Command/Development/RequiresDevModeTrait.php(20): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#3 src/vendor/symfony/console/Application.php(992): XF\Cli\Command\Development\EntityClassProperties->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#4 src/vendor/symfony/console/Application.php(255): Symfony\Component\Console\Application->doRunCommand(Object(XF\Cli\Command\Development\EntityClassProperties), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#5 src/vendor/symfony/console/Application.php(148): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#6 src/XF/Cli/Runner.php(111): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#7 cmd.php(15): XF\Cli\Runner->run()
#8 {main}

In PHP 8.0, it creates the following output:
1633347742167.webp

You'll notice that the preceding slash from \XF\Phrase has vanished, which renders this relation hint unusable as this is an invalid namespace.

This is the getType getter:
PHP:
    /**
     * @return \XF\Phrase
     */
    public function getType(): \XF\Phrase
    {
        return \XF::phrase('addon_prefix_role_type.' . $this->role_type);
    }

I've had to revert to the 2.2.6 "DocBlock-only" method of obtaining return hints in order to resolve this issue.
 
I had actually ran into this myself a few weeks ago and sorted it. I've also made changes so that PHPDoc annotations are preferred if present, since they allow you to be more explicit when it comes to generics/arrays.

Diff:
diff --git a/src/XF/Cli/Command/Development/EntityClassProperties.php b/src/XF/Cli/Command/Development/EntityClassProperties.php
index 6ec381fdfb..f49a12d284 100644
--- a/src/XF/Cli/Command/Development/EntityClassProperties.php
+++ b/src/XF/Cli/Command/Development/EntityClassProperties.php
@@ -132,21 +132,63 @@ class EntityClassProperties extends Command
                 }
                 $method = $reflection->getMethod($methodName);
 
-                $type = null;
-                if ($method->getReturnType())
+                $comment = $method->getDocComment();
+                $returnType = $method->getReturnType();
+                if ($comment && preg_match('/^\s*?\*\s*?@return\s+(\S+)/mi', $comment, $matches))
                 {
-                    $type = strval($method->getReturnType());
+                    $type = $matches[1];
                 }
-                if ($type === null)
+                else if ($returnType)
                 {
-                    $comment = $method->getDocComment();
-                    if ($comment)
+                    if ($returnType instanceof \ReflectionUnionType)
                     {
-                        if (preg_match('/^\s*?\*\s*?@return\s+(\S+)/mi', $comment, $matches))
+                        $returnTypes = $returnType->getTypes();
+                    }
+                    else
+                    {
+                        $returnTypes = [$returnType];
+                    }
+
+                    $types = [];
+                    $nullable = false;
+                    foreach ($returnTypes AS $returnType)
+                    {
+                        if (PHP_VERSION_ID < 70100)
+                        {
+                            $types[] = (string) $returnType;
+                        }
+                        else
                         {
-                            $type = $matches[1];
+                            if ($returnType->getName() === 'null')
+                            {
+                                $nullable = true;
+                            }
+                            else
+                            {
+                                $types[] = ($returnType->isBuiltin() ? '' : '\\')
+                                    . $returnType->getName();
+
+                                if ($returnType->allowsNull())
+                                {
+                                    $nullable = true;
+                                }
+                            }
                         }
                     }
+
+                    $type = implode('|', $types);
+                    if ($nullable)
+                    {
+                        if (strlen($type))
+                        {
+                            $type .= '|';
+                        }
+
+                        $type .= 'null';
+                    }
+                }
+                else
+                {
+                    $type = null;
                 }
 
                 $getters[$getter] = [

Edit: Improved PHP 7.0 backwards compatibility and implemented support for PHP 8 union types.
 
Last edited:
Top Bottom