* @license MIT */ abstract class Tools { /** * Replace node of one type with another. * * @param Node $node Original node * @param string $class Class of new node * @param array $propertyMap Property map between old and new objects * * @psalm-param class-string $class Class of new node * @psalm-param array $propertyMap Property map between old and new objects * * @return Node */ public static function replaceType(Node $node, string $class, array $propertyMap = []): Node { if ($propertyMap) { $nodeNew = (new ReflectionClass($class))->newInstanceWithoutConstructor(); foreach ($propertyMap as $old => $new) { $nodeNew->{$new} = $node->{$old}; } $nodeNew->setAttributes($node->getAttributes()); return $nodeNew; } return new $class(...[ ...\array_map(fn (string $name) => $node->{$name}, $node->getSubNodeNames()), $node->getAttributes() ]); } /** * Replace type in-place. * * @param Node &$node Original node * @param string $class Class of new node * @param array $propertyMap Property map between old and new objects * * @psalm-param class-string $class Class of new node * @psalm-param array $propertyMap Property map between old and new objects * * @param-out Node &$node * * @return void */ public static function replaceTypeInPlace(Node &$node, string $class, array $propertyMap = []): void { $node = self::replaceType($node, $class, $propertyMap); } /** * Create variable assignment. * * @param Variable $name Variable * @param Expr $expression Expression * * @return Expression */ public static function assign(Variable $name, Expr $expression): Expression { return new Expression( new Assign( $name, $expression ) ); } /** * Call function. * * @param array{0: class-string, 1: string}|callable-string $name Function name * @param Expr|Arg ...$parameters Parameters * * @return FuncCall|StaticCall */ public static function call($name, ...$parameters) { $parameters = \array_map(fn ($data) => $data instanceof Arg ? $data : new Arg($data), $parameters); return \is_array($name) ? new StaticCall(new Name($name[0]), $name[1], $parameters) : new FuncCall(new Name($name), $parameters); } /** * Call method of object. * * @param Expr $name Object name * @param string $method Method * @param Expr|Arg ...$parameters Parameters * * @return MethodCall */ public static function callMethod(Expr $name, string $method, ...$parameters): MethodCall { $parameters = \array_map(fn ($data) => $data instanceof Arg ? $data : new Arg($data), $parameters); return new MethodCall($name, $method, $parameters); } /** * Convert array, int or other literal to node. * * @param mixed $data Data to convert * * @return Node */ public static function toLiteral($data): Node { return self::toNode(\var_export($data, true)); } /** * Convert code to node. * * @param string $code Code * * @memoize $code * * @return Node */ public static function toNode(string $code): Node { $res = (new ParserFactory)->create(ParserFactory::PREFER_PHP7)->parse('expr)) { throw new \RuntimeException('Invalid code was provided!'); } return $res[0]->expr; } /** * Check if this node or any child node have any side effects (like calling other methods, or assigning variables). * * @param ?Expr $node Node * * @return bool */ public static function hasSideEffects(?Expr $node): bool { if (!$node) { return false; } if ($node->hasAttribute('hasSideEffects') || $node instanceof String_ // __toString || $node instanceof ArrayDimFetch // offsetSet/offsetGet || $node instanceof Assign || $node instanceof AssignOp || $node instanceof AssignRef || $node instanceof Clone_ // __clone || $node instanceof Eval_ || $node instanceof FuncCall || $node instanceof Include_ || $node instanceof List_ // offsetGet/offsetSet || $node instanceof New_ || $node instanceof NullsafeMethodCall || $node instanceof NullsafePropertyFetch || $node instanceof PostDec || $node instanceof PostInc || $node instanceof PreDec || $node instanceof PreInc || $node instanceof PropertyFetch || $node instanceof StaticCall || $node instanceof Yield_ || $node instanceof YieldFrom ) { $node->setAttribute('hasSideEffects', true); return true; } foreach ($node->getAttributes() as $name) { if ($node->{$name} instanceof Expr) { if (self::hasSideEffects($node->{$name})) { $node->setAttribute('hasSideEffects', true); return true; } } elseif (\is_array($node->{$name})) { foreach ($node->{$name} as $var) { if ($var instanceof Expr && self::hasSideEffects($var)) { $node->setAttribute('hasSideEffects', true); return true; } } } } return false; } /** * Create a new object extended from this object, with the specified additional trait + interface * * @param object $obj * @param string $trait * @param string $interface * * @return object */ public static function cloneWithTrait(object $obj, string $trait, string $interface): object { $reflect = new ReflectionClass($obj); $r = $reflect; while ($r && $r->isAnonymous()) { $r = $r->getParentClass(); } $extend = "extends \\".$r->getName(); $eval = "\$newObj = new class $extend implements \\$interface { use \\$trait; public function __construct() {} };"; eval($eval); $reflectNew = new ReflectionClass($newObj); do { if ($tmp = $reflectNew->getParentClass()) { $reflectNew = $tmp; } foreach ($reflect->getProperties() as $prop) { if ($reflectNew->hasProperty($prop->getName())) { $propNew = $reflectNew->getProperty($prop->getName()); $propNew->setAccessible(true); $prop->setAccessible(true); $propNew->setValue($newObj, $prop->getValue($obj)); } } } while ($reflect = $reflect->getParentClass()); return $newObj; } /** * Checks private property exists in an object. * * @param object $obj Object * @param string $var Attribute name * * @psalm-suppress InvalidScope * * @return bool * @access public */ public static function hasVar($obj, string $var): bool { return \Closure::bind( function () use ($var) { return isset($this->{$var}); }, $obj, \get_class($obj) )->__invoke(); } /** * Accesses a private variable from an object. * * @param object $obj Object * @param string $var Attribute name * * @psalm-suppress InvalidScope * * @return mixed * @access public */ public static function &getVar($obj, string $var) { return \Closure::bind( function & () use ($var) { return $this->{$var}; }, $obj, \get_class($obj) )->__invoke(); } /** * Sets a private variable in an object. * * @param object $obj Object * @param string $var Attribute name * @param mixed $val Attribute value * * @psalm-suppress InvalidScope * * @return void * * @access public */ public static function setVar($obj, string $var, &$val): void { \Closure::bind( function () use ($var, &$val) { $this->{$var} =& $val; }, $obj, \get_class($obj) )->__invoke(); } }