<NME> TypeGenerator.php
<BEF> <?php
/**
 * Zend Framework (http://framework.zend.com/)
 *
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://framework.zend.com/license/new-bsd New BSD License
 */

namespace Zend\Code\Generator;

use Zend\Code\Generator\Exception\InvalidArgumentException;

use function in_array;
use function ltrim;
use function preg_match;
use function sprintf;
use function strpos;
use function strtolower;
use function substr;

final class TypeGenerator implements GeneratorInterface
     */
    private $type;

    /**
     * @var string[]
     *
    /**
     * @var bool
     */
    private $nullable;

    /**
     * @var string a regex pattern to match valid class names or types
     */
    private static $validIdentifierMatcher = '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*(\\\\[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)*$/';
    // @codingStandardsIgnoreEnd

    /**
        'int',
        'float',
        'string',
        'bool',
        'array',
     */
    public static function fromTypeString($type)
    {
        list($wasTrimmed, $trimmedType) = self::trimType($type);

        if (! preg_match(self::$validIdentifierMatcher, $trimmedType)) {
            throw new InvalidArgumentException(sprintf(
    private static $validIdentifierMatcher = '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*'
        . '(\\\\[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)*$/';

    /**
     * @param string $type
     *
     * @return TypeGenerator
     *
     * @throws InvalidArgumentException
     */
    public static function fromTypeString($type)
    {
        list($nullable, $trimmedNullable) = self::trimNullable($type);
        list($wasTrimmed, $trimmedType) = self::trimType($trimmedNullable);

        $instance = new self();

        $instance->type              = $trimmedType;
        $instance->isInternalPhpType = self::isInternalPhpType($trimmedType);

        return $instance;

        $isInternalPhpType = self::isInternalPhpType($trimmedType);

        if ($wasTrimmed && $isInternalPhpType) {
            throw new InvalidArgumentException(sprintf(
                'Provided type "%s" is an internal PHP type, but was provided with a namespace separator prefix',
                $type
            ));
     */
    public function generate()
    {
        if ($this->isInternalPhpType) {
            return strtolower($this->type);
        }

        return '\\' . $this->type;
    }

    /**
    }
     */
    public function __toString()
    {
        return ltrim($this->generate(), '\\');
    }

    /**
    public function __toString()
    {
        return ltrim($this->generate(), '?\\');
    }

    /**
     * @param string $type
     *
     * @return bool[]|string[] ordered tuple, first key represents whether the type is nullable, second is the
     *                         trimmed string
     */
    private static function trimNullable($type)
    {
        if (0 === strpos($type, '?')) {
            return [true, substr($type, 1)];
        }

        return [false, $type];
    }

    /**
     * @param string $type
     *
     * @return bool[]|string[] ordered tuple, first key represents whether the values was trimmed, second is the
     *                         trimmed string
     */
    private static function trimType($type)
    {
        if (0 === strpos($type, '\\')) {
            return [true, substr($type, 1)];
        }

        return [false, $type];
    }

    /**
     * @param string $type
     *
     * @return bool
     */
    private static function isInternalPhpType($type)
    {
        return in_array(strtolower($type), self::$internalPhpTypes, true);
    }
}

<MSG> Supporting nullable type hints in the `TypeGenerator`

<DFF> @@ -23,6 +23,11 @@ final class TypeGenerator implements GeneratorInterface
      */
     private $type;
 
+    /**
+     * @var bool
+     */
+    private $nullable;
+
     /**
      * @var string[]
      *
@@ -34,7 +39,7 @@ final class TypeGenerator implements GeneratorInterface
     /**
      * @var string a regex pattern to match valid class names or types
      */
-    private static $validIdentifierMatcher = '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*(\\\\[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)*$/';
+    private static $validIdentifierMatcher = '/^\??[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*(\\\\[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)*$/';
     // @codingStandardsIgnoreEnd
 
     /**
@@ -46,7 +51,8 @@ final class TypeGenerator implements GeneratorInterface
      */
     public static function fromTypeString($type)
     {
-        list($wasTrimmed, $trimmedType) = self::trimType($type);
+        list($nullable, $trimmedNullable) = self::trimNullable($type);
+        list($wasTrimmed, $trimmedType) = self::trimType($trimmedNullable);
 
         if (! preg_match(self::$validIdentifierMatcher, $trimmedType)) {
             throw new InvalidArgumentException(sprintf(
@@ -68,6 +74,7 @@ final class TypeGenerator implements GeneratorInterface
         $instance = new self();
 
         $instance->type              = $trimmedType;
+        $instance->nullable          = $nullable;
         $instance->isInternalPhpType = self::isInternalPhpType($trimmedType);
 
         return $instance;
@@ -82,11 +89,13 @@ final class TypeGenerator implements GeneratorInterface
      */
     public function generate()
     {
+        $nullable = $this->nullable ? '?' : '';
+
         if ($this->isInternalPhpType) {
-            return strtolower($this->type);
+            return $nullable . strtolower($this->type);
         }
 
-        return '\\' . $this->type;
+        return $nullable . '\\' . $this->type;
     }
 
     /**
@@ -94,7 +103,22 @@ final class TypeGenerator implements GeneratorInterface
      */
     public function __toString()
     {
-        return ltrim($this->generate(), '\\');
+        return ltrim($this->generate(), '?\\');
+    }
+
+    /**
+     * @param string $type
+     *
+     * @return bool[]|int[] ordered tuple, first key represents whether the type is nullable, second is the
+     *                      trimmed string
+     */
+    private static function trimNullable($type)
+    {
+        if (0 === strpos($type, '?')) {
+            return [true, substr($type, 1)];
+        }
+
+        return [false, $type];
     }
 
     /**

Supporting nullable type hints in the `TypeGenerator`