1<?php declare(strict_types=1); 2 3namespace PhpParser; 4 5use PhpParser\Node\Expr; 6use PhpParser\Node\Identifier; 7use PhpParser\Node\Name; 8use PhpParser\Node\NullableType; 9use PhpParser\Node\Scalar; 10use PhpParser\Node\Stmt; 11use PhpParser\Node\UnionType; 12 13/** 14 * This class defines helpers used in the implementation of builders. Don't use it directly. 15 * 16 * @internal 17 */ 18final class BuilderHelpers 19{ 20 /** 21 * Normalizes a node: Converts builder objects to nodes. 22 * 23 * @param Node|Builder $node The node to normalize 24 * 25 * @return Node The normalized node 26 */ 27 public static function normalizeNode($node) : Node { 28 if ($node instanceof Builder) { 29 return $node->getNode(); 30 } elseif ($node instanceof Node) { 31 return $node; 32 } 33 34 throw new \LogicException('Expected node or builder object'); 35 } 36 37 /** 38 * Normalizes a node to a statement. 39 * 40 * Expressions are wrapped in a Stmt\Expression node. 41 * 42 * @param Node|Builder $node The node to normalize 43 * 44 * @return Stmt The normalized statement node 45 */ 46 public static function normalizeStmt($node) : Stmt { 47 $node = self::normalizeNode($node); 48 if ($node instanceof Stmt) { 49 return $node; 50 } 51 52 if ($node instanceof Expr) { 53 return new Stmt\Expression($node); 54 } 55 56 throw new \LogicException('Expected statement or expression node'); 57 } 58 59 /** 60 * Normalizes strings to Identifier. 61 * 62 * @param string|Identifier $name The identifier to normalize 63 * 64 * @return Identifier The normalized identifier 65 */ 66 public static function normalizeIdentifier($name) : Identifier { 67 if ($name instanceof Identifier) { 68 return $name; 69 } 70 71 if (\is_string($name)) { 72 return new Identifier($name); 73 } 74 75 throw new \LogicException('Expected string or instance of Node\Identifier'); 76 } 77 78 /** 79 * Normalizes strings to Identifier, also allowing expressions. 80 * 81 * @param string|Identifier|Expr $name The identifier to normalize 82 * 83 * @return Identifier|Expr The normalized identifier or expression 84 */ 85 public static function normalizeIdentifierOrExpr($name) { 86 if ($name instanceof Identifier || $name instanceof Expr) { 87 return $name; 88 } 89 90 if (\is_string($name)) { 91 return new Identifier($name); 92 } 93 94 throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr'); 95 } 96 97 /** 98 * Normalizes a name: Converts string names to Name nodes. 99 * 100 * @param Name|string $name The name to normalize 101 * 102 * @return Name The normalized name 103 */ 104 public static function normalizeName($name) : Name { 105 return self::normalizeNameCommon($name, false); 106 } 107 108 /** 109 * Normalizes a name: Converts string names to Name nodes, while also allowing expressions. 110 * 111 * @param Expr|Name|string $name The name to normalize 112 * 113 * @return Name|Expr The normalized name or expression 114 */ 115 public static function normalizeNameOrExpr($name) { 116 return self::normalizeNameCommon($name, true); 117 } 118 119 /** 120 * Normalizes a name: Converts string names to Name nodes, optionally allowing expressions. 121 * 122 * @param Expr|Name|string $name The name to normalize 123 * @param bool $allowExpr Whether to also allow expressions 124 * 125 * @return Name|Expr The normalized name, or expression (if allowed) 126 */ 127 private static function normalizeNameCommon($name, bool $allowExpr) { 128 if ($name instanceof Name) { 129 return $name; 130 } elseif (is_string($name)) { 131 if (!$name) { 132 throw new \LogicException('Name cannot be empty'); 133 } 134 135 if ($name[0] === '\\') { 136 return new Name\FullyQualified(substr($name, 1)); 137 } elseif (0 === strpos($name, 'namespace\\')) { 138 return new Name\Relative(substr($name, strlen('namespace\\'))); 139 } else { 140 return new Name($name); 141 } 142 } 143 144 if ($allowExpr) { 145 if ($name instanceof Expr) { 146 return $name; 147 } 148 throw new \LogicException( 149 'Name must be a string or an instance of Node\Name or Node\Expr' 150 ); 151 } else { 152 throw new \LogicException('Name must be a string or an instance of Node\Name'); 153 } 154 } 155 156 /** 157 * Normalizes a type: Converts plain-text type names into proper AST representation. 158 * 159 * In particular, builtin types become Identifiers, custom types become Names and nullables 160 * are wrapped in NullableType nodes. 161 * 162 * @param string|Name|Identifier|NullableType|UnionType $type The type to normalize 163 * 164 * @return Name|Identifier|NullableType|UnionType The normalized type 165 */ 166 public static function normalizeType($type) { 167 if (!is_string($type)) { 168 if ( 169 !$type instanceof Name && !$type instanceof Identifier && 170 !$type instanceof NullableType && !$type instanceof UnionType 171 ) { 172 throw new \LogicException( 173 'Type must be a string, or an instance of Name, Identifier, NullableType or UnionType' 174 ); 175 } 176 return $type; 177 } 178 179 $nullable = false; 180 if (strlen($type) > 0 && $type[0] === '?') { 181 $nullable = true; 182 $type = substr($type, 1); 183 } 184 185 $builtinTypes = [ 186 'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object', 'mixed' 187 ]; 188 189 $lowerType = strtolower($type); 190 if (in_array($lowerType, $builtinTypes)) { 191 $type = new Identifier($lowerType); 192 } else { 193 $type = self::normalizeName($type); 194 } 195 196 if ($nullable && (string) $type === 'void') { 197 throw new \LogicException('void type cannot be nullable'); 198 } 199 200 if ($nullable && (string) $type === 'mixed') { 201 throw new \LogicException('mixed type cannot be nullable'); 202 } 203 204 return $nullable ? new NullableType($type) : $type; 205 } 206 207 /** 208 * Normalizes a value: Converts nulls, booleans, integers, 209 * floats, strings and arrays into their respective nodes 210 * 211 * @param Node\Expr|bool|null|int|float|string|array $value The value to normalize 212 * 213 * @return Expr The normalized value 214 */ 215 public static function normalizeValue($value) : Expr { 216 if ($value instanceof Node\Expr) { 217 return $value; 218 } elseif (is_null($value)) { 219 return new Expr\ConstFetch( 220 new Name('null') 221 ); 222 } elseif (is_bool($value)) { 223 return new Expr\ConstFetch( 224 new Name($value ? 'true' : 'false') 225 ); 226 } elseif (is_int($value)) { 227 return new Scalar\LNumber($value); 228 } elseif (is_float($value)) { 229 return new Scalar\DNumber($value); 230 } elseif (is_string($value)) { 231 return new Scalar\String_($value); 232 } elseif (is_array($value)) { 233 $items = []; 234 $lastKey = -1; 235 foreach ($value as $itemKey => $itemValue) { 236 // for consecutive, numeric keys don't generate keys 237 if (null !== $lastKey && ++$lastKey === $itemKey) { 238 $items[] = new Expr\ArrayItem( 239 self::normalizeValue($itemValue) 240 ); 241 } else { 242 $lastKey = null; 243 $items[] = new Expr\ArrayItem( 244 self::normalizeValue($itemValue), 245 self::normalizeValue($itemKey) 246 ); 247 } 248 } 249 250 return new Expr\Array_($items); 251 } else { 252 throw new \LogicException('Invalid value'); 253 } 254 } 255 256 /** 257 * Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc. 258 * 259 * @param Comment\Doc|string $docComment The doc comment to normalize 260 * 261 * @return Comment\Doc The normalized doc comment 262 */ 263 public static function normalizeDocComment($docComment) : Comment\Doc { 264 if ($docComment instanceof Comment\Doc) { 265 return $docComment; 266 } elseif (is_string($docComment)) { 267 return new Comment\Doc($docComment); 268 } else { 269 throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc'); 270 } 271 } 272 273 /** 274 * Adds a modifier and returns new modifier bitmask. 275 * 276 * @param int $modifiers Existing modifiers 277 * @param int $modifier Modifier to set 278 * 279 * @return int New modifiers 280 */ 281 public static function addModifier(int $modifiers, int $modifier) : int { 282 Stmt\Class_::verifyModifier($modifiers, $modifier); 283 return $modifiers | $modifier; 284 } 285} 286