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