Add funcCall(), methodCall() and staticCall() builders

This commit is contained in:
Nikita Popov 2018-03-03 15:40:51 +01:00
parent 6aba7624ed
commit b998d1e9b5
7 changed files with 207 additions and 8 deletions

View File

@ -1,7 +1,15 @@
Version 4.0.1-dev
-----------------
Nothing yet.
### Changed
* Added checks to node traverser to prevent replacing a statement with an expression or vice versa.
This should prevent common mistakes in the implementation of node visitors.
### Added
* Added `funcCall()`, `methodCall()` and `staticCall()` methods to `BuilderFactory`, to simplify
creation of call nodes.
Version 4.0.0 (2018-02-28)
--------------------------

View File

@ -100,6 +100,12 @@ nodes. The following methods are currently available:
* `val($value)`: Creates an AST node for a literal value like `42` or `[1, 2, 3]`.
* `args(array $args)`: Creates an array of function/method arguments, including the required `Arg`
wrappers. Also converts literals to AST nodes.
* `funcCall($name, array $args = [])`: Create a function call node. Converts `$name` to a `Name`
node and normalizes arguments.
* `methodCall(Expr $var, $name, array $args = [])`: Create a method call node. Converts `$name` to
an `Identifier` node and normalizes arguments.
* `staticCall($class, $name, array $args = [])`: Create a static method call node. Converts
`$class` to a `Name` node, `$name` to an `Identifier` node and normalizes arguments.
* `concat(...$exprs)`: Create a tree of `BinaryOp\Concat` nodes for the given expressions.
These methods may be expanded on an as-needed basis. Please open an issue or PR if a common

View File

@ -5,6 +5,8 @@ namespace PhpParser;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Use_;
@ -141,6 +143,55 @@ class BuilderFactory
return $normalizedArgs;
}
/**
* Creates a function call node.
*
* @param string|Name|Expr $name Function name
* @param array $args Function arguments
*
* @return Expr\FuncCall
*/
public function funcCall($name, array $args = []) : Expr\FuncCall {
return new Expr\FuncCall(
BuilderHelpers::normalizeNameOrExpr($name),
$this->args($args)
);
}
/**
* Creates a method call node.
*
* @param Expr $var Variable the method is called on
* @param string|Identifier|Expr $name Method name
* @param array $args Method arguments
*
* @return Expr\MethodCall
*/
public function methodCall(Expr $var, $name, array $args = []) : Expr\MethodCall {
return new Expr\MethodCall(
$var,
$this->normalizeIdentifierOrExpr($name),
$this->args($args)
);
}
/**
* Creates a static method call node.
*
* @param string|Name|Expr $class Class name
* @param string|Identifier|Expr $name Method name
* @param array $args Method arguments
*
* @return Expr\StaticCall
*/
public function staticCall($class, $name, array $args = []) : Expr\StaticCall {
return new Expr\StaticCall(
BuilderHelpers::normalizeNameOrExpr($class),
$this->normalizeIdentifierOrExpr($name),
$this->args($args)
);
}
/**
* Creates nested Concat nodes from a list of expressions.
*
@ -161,15 +212,35 @@ class BuilderFactory
return $lastConcat;
}
private function normalizeStringExpr($expr) {
/**
* @param string|Expr $expr
* @return Expr
*/
private function normalizeStringExpr($expr) : Expr {
if ($expr instanceof Expr) {
return $expr;
}
if (is_string($expr)) {
if (\is_string($expr)) {
return new String_($expr);
}
throw new \LogicException('Expected string or Expr');
}
/**
* @param string|Identifier|Expr $name
* @return Identifier|Expr
*/
private function normalizeIdentifierOrExpr($name) {
if ($name instanceof Identifier || $name instanceof Expr) {
return $name;
}
if (\is_string($name)) {
return new Identifier($name);
}
throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr');
}
}

View File

@ -56,13 +56,36 @@ final class BuilderHelpers
}
/**
* Normalizes a name: Converts plain string names to PhpParser\Node\Name.
* Normalizes a name: Converts string names to Name nodes.
*
* @param Name|string $name The name to normalize
*
* @return Name The normalized name
*/
public static function normalizeName($name) : Name {
return self::normalizeNameCommon($name, false);
}
/**
* Normalizes a name: Converts string names to Name nodes, while also allowing expressions.
*
* @param Expr|Name|string $name The name to normalize
*
* @return Name|Expr The normalized name or expression
*/
public static function normalizeNameOrExpr($name) {
return self::normalizeNameCommon($name, true);
}
/**
* Normalizes a name: Converts string names to Name nodes, optionally allowing expressions.
*
* @param Expr|Name|string $name The name to normalize
* @param bool $allowExpr Whether to also allow expressions
*
* @return Name|Expr The normalized name, or expression (if allowed)
*/
private static function normalizeNameCommon($name, bool $allowExpr) {
if ($name instanceof Name) {
return $name;
} elseif (is_string($name)) {
@ -79,7 +102,16 @@ final class BuilderHelpers
}
}
throw new \LogicException('Name must be a string or an instance of PhpParser\Node\Name');
if ($allowExpr) {
if ($name instanceof Expr) {
return $name;
}
throw new \LogicException(
'Name must be a string or an instance of Node\Name or Node\Expr'
);
} else {
throw new \LogicException('Name must be a string or an instance of Node\Name');
}
}
/**

View File

@ -16,8 +16,8 @@ class FuncCall extends Expr
* Constructs a function call node.
*
* @param Node\Name|Expr $name Function name
* @param Node\Arg[] $args Arguments
* @param array $attributes Additional attributes
* @param Node\Arg[] $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($name, array $args = [], array $attributes = []) {
parent::__construct($attributes);

View File

@ -153,7 +153,7 @@ DOC;
/**
* @expectedException \LogicException
* @expectedExceptionMessage Name must be a string or an instance of PhpParser\Node\Name
* @expectedExceptionMessage Name must be a string or an instance of Node\Name
*/
public function testInvalidName() {
$this->createClassBuilder('Test')

View File

@ -6,8 +6,12 @@ use PhpParser\Builder;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Yaml\Tests\A;
class BuilderFactoryTest extends TestCase
{
@ -92,6 +96,84 @@ class BuilderFactoryTest extends TestCase
);
}
public function testCalls() {
$factory = new BuilderFactory();
// Simple function call
$this->assertEquals(
new Expr\FuncCall(
new Name('var_dump'),
[new Arg(new String_('str'))]
),
$factory->funcCall('var_dump', ['str'])
);
// Dynamic function call
$this->assertEquals(
new Expr\FuncCall(new Expr\Variable('fn')),
$factory->funcCall(new Expr\Variable('fn'))
);
// Simple method call
$this->assertEquals(
new Expr\MethodCall(
new Expr\Variable('obj'),
new Identifier('method'),
[new Arg(new LNumber(42))]
),
$factory->methodCall(new Expr\Variable('obj'), 'method', [42])
);
// Explicitly pass Identifier node
$this->assertEquals(
new Expr\MethodCall(
new Expr\Variable('obj'),
new Identifier('method')
),
$factory->methodCall(new Expr\Variable('obj'), new Identifier('method'))
);
// Dynamic method call
$this->assertEquals(
new Expr\MethodCall(
new Expr\Variable('obj'),
new Expr\Variable('method')
),
$factory->methodCall(new Expr\Variable('obj'), new Expr\Variable('method'))
);
// Simple static method call
$this->assertEquals(
new Expr\StaticCall(
new Name\FullyQualified('Foo'),
new Identifier('bar'),
[new Arg(new Expr\Variable('baz'))]
),
$factory->staticCall('\Foo', 'bar', [new Expr\Variable('baz')])
);
// Dynamic static method call
$this->assertEquals(
new Expr\StaticCall(
new Expr\Variable('foo'),
new Expr\Variable('bar')
),
$factory->staticCall(new Expr\Variable('foo'), new Expr\Variable('bar'))
);
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Expected string or instance of Node\Identifier or Node\Expr
*/
public function testInvalidIdentifier() {
(new BuilderFactory())->staticCall('Foo', new Name('bar'));
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Name must be a string or an instance of Node\Name or Node\Expr
*/
public function testInvalidNameOrExpr() {
(new BuilderFactory())->funcCall(new Node\Stmt\Return_());
}
public function testIntegration() {
$factory = new BuilderFactory;
$node = $factory->namespace('Name\Space')