*/ private static $keywordTypes = [ 'array' => 'array', 'bool' => 'bool', 'callable' => 'callable', 'false' => 'false', 'float' => 'float', 'int' => 'int', 'iterable' => 'iterable', 'mixed' => 'mixed', 'never' => 'never', 'null' => 'null', 'object' => 'object', 'parent' => 'parent', 'self' => 'self', 'static' => 'static', 'string' => 'string', 'true' => 'true', 'void' => 'void', ]; /** * Retrieve a list of all PHP native keyword types. * * @since 1.1.0 * * @return array Key and value both contain the type name in lowercase. */ public static function getKeywordTypes() { return self::$keywordTypes; } /** * Check if a singular type is a PHP native keyword based type. * * @since 1.1.0 * * @param string $type The singular type. * * @return bool */ public static function isKeyword($type) { if (\is_string($type) === false) { return false; } $typeLC = \strtolower(\ltrim(\trim($type), '\\')); return isset(self::$keywordTypes[$typeLC]); } /** * Normalize the case for a single type. * * - Types which are recognized PHP "keyword" types will be returned in lowercase. * - Types which are recognized PHP "keyword" types and are incorrectly provided as fully qualified * (typically: true/false/null) will be returned as unqualified. * - Class/Interface/Enum names will be returned in their original case. * * @since 1.1.0 * @since 1.1.2 Will now also normalize (illegal) FQN true/false/null to unqualified. * * @param string $type Type to normalize the case for. * * @return string The case-normalized type or an empty string if the input was invalid. */ public static function normalizeCase($type) { if (\is_string($type) === false) { return ''; } if (self::isKeyword($type)) { return \strtolower(\ltrim($type, '\\')); } return $type; } /** * Check if a type string represents a plain, singular type. * * Note: Nullable types are not considered plain, singular types for the purposes of this method. * * @since 1.1.0 * * @param string $typeString Type string. * * @return bool */ public static function isSingular($typeString) { if (\is_string($typeString) === false) { return false; } $typeString = \trim($typeString); return empty($typeString) === false && \strpos($typeString, '?') === false && \strpos($typeString, '|') === false && \strpos($typeString, '&') === false && \strpos($typeString, '(') === false && \strpos($typeString, ')') === false; } /** * Check if a type string represents a nullable type. * * A nullable type in the context of this method is a type which * - starts with the nullable operator and has something after it which is being made nullable; * - or contains `null` as part of a union or DNF type. * * A stand-alone `null` type is not considered a nullable type, but a singular type. * * @since 1.1.0 * * @param string $typeString Type string. * * @return bool */ public static function isNullable($typeString) { if (\is_string($typeString) === false) { return false; } $typeString = \trim($typeString); if (empty($typeString) === true) { return false; } // Check for plain nullable type with something which is being made nullable. if (\preg_match('`^\?\s*[^|&()?\s]+`', $typeString) === 1) { return true; } // Check for nullable union type. $matched = \preg_match( '`(?^|[^|&(?\s]+\s*\|)\s*[\\\\]?null\s*(?\|\s*[^|&)?\s]+|$)`i', $typeString, $matches ); return ($matched === 1 && (empty($matches['before']) === false || empty($matches['after']) === false)); } /** * Check if a type string represents a pure union type. * * Note: DNF types are not considered union types for the purpose of this method. * * @since 1.1.0 * * @param string $typeString Type string. * * @return bool */ public static function isUnion($typeString) { return \is_string($typeString) && \strpos($typeString, '?') === false && \strpos($typeString, '|') !== false && \strpos($typeString, '&') === false && \strpos($typeString, '(') === false && \strpos($typeString, ')') === false // Make sure there is always something before and after each |. && \preg_match('`^[^|&()?\s]+(\s*\|\s*[^|&()?\s]+)+$`', $typeString) === 1; } /** * Check if a type string represents a pure intersection type. * * Note: DNF types are not considered intersection types for the purpose of this method. * * @since 1.1.0 * * @param string $typeString Type string. * * @return bool */ public static function isIntersection($typeString) { return \is_string($typeString) && \strpos($typeString, '?') === false && \strpos($typeString, '|') === false && \strpos($typeString, '&') !== false && \strpos($typeString, '(') === false && \strpos($typeString, ')') === false // Make sure there is always something before and after each &. && \preg_match('`^[^|&()?\s]+(\s*&\s*[^|&()?\s]+)+$`', $typeString) === 1; } /** * Check if a type string represents a disjunctive normal form (DNF) type. * * This check for a strict * * @since 1.1.0 * * @param string $typeString Type string. * * @return bool */ public static function isDNF($typeString) { return \is_string($typeString) && \strpos($typeString, '?') === false && \strpos($typeString, '|') !== false && \strpos($typeString, '&') !== false && \strpos($typeString, '(') !== false && \strpos($typeString, ')') !== false // Now make sure that it is not a definitely invalid format. && \preg_match(self::INVALID_DNF_REGEX, $typeString) !== 1; } /** * Split a type string to its individual types and optionally normalize the case of the types. * * @since 1.1.0 * * @param string $typeString Type to split. * @param bool $normalize Whether or not to normalize the case of types. * Defaults to true. * * @return array List containing all seen types in the order they were encountered. * * @throws \PHPCSUtils\Exceptions\TypeError If passed $typeString is not a string. */ public static function toArray($typeString, $normalize = true) { if (\is_string($typeString) === false) { throw TypeError::create(1, '$typeString', 'string', $typeString); } if (\trim($typeString) === '') { return []; } $addNull = false; if ($typeString[0] === '?') { $addNull = true; $typeString = \substr($typeString, 1); } $typeString = \preg_replace('`\s+`', '', $typeString); $types = \preg_split('`[|&()]+`', $typeString, -1, \PREG_SPLIT_NO_EMPTY); // Normalize the types. if ($normalize === true) { $types = \array_map([__CLASS__, 'normalizeCase'], $types); } if ($addNull === true) { \array_unshift($types, 'null'); } return $types; } /** * Split a type string to the unique types included and optionally normalize the case of the types. * * @since 1.1.0 * * @param string $typeString Type to split. * @param bool $normalize Whether or not to normalize the case of types. * Defaults to true. * * @return array Associative array with the unique types as both the key as well as the value. * * @throws \PHPCSUtils\Exceptions\TypeError If passed $typeString is not a string. */ public static function toArrayUnique($typeString, $normalize = true) { $types = self::toArray($typeString, $normalize); return \array_combine($types, $types); } /** * Filter a list of types down to only the keyword based types. * * @since 1.1.0 * * @param array $types Array of types. * Typically, this is an array as retrieved from the * {@see TypeString::toArray()} method or the * {@see TypeString::toArrayUnique()} method. * * @return array Array with only the PHP native keyword based types. * The result may be an empty array if the input array didn't contain * any keyword based types or if the input was invalid. */ public static function filterKeywordTypes(array $types) { return \array_filter($types, [__CLASS__, 'isKeyword']); } /** * Filter a list of types down to only the OO name based types. * * @since 1.1.0 * * @param array $types Array of types. * Typically, this is an array as retrieved from the * {@see TypeString::toArray()} method or the * {@see TypeString::toArrayUnique()} method. * * @return array Array with only the OO name based types. * The result may be an empty array if the input array didn't contain * any OO name based types or if the input was invalid. */ public static function filterOOTypes(array $types) { return \array_filter( $types, static function ($type) { return \is_string($type) === true && self::isKeyword($type) === false; } ); } }