Skip to content

Commit

Permalink
Added CompiledValue - value and constant name are resolved in one step
Browse files Browse the repository at this point in the history
  • Loading branch information
kukulich committed Oct 12, 2021
1 parent b946611 commit 165dee9
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 196 deletions.
126 changes: 68 additions & 58 deletions src/NodeCompiler/CompileNodeToValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
use function constant;
use function defined;
use function dirname;
use function explode;
use function in_array;
use function realpath;
use function sprintf;

Expand All @@ -28,23 +30,32 @@ class CompileNodeToValue
*
* @param Node\Stmt\Expression|Node\Expr $node Node has to be processed by the PhpParser\NodeVisitor\NameResolver
*
* @return scalar|array<scalar>|null
*
* @throws Exception\UnableToCompileNode
*/
public function __invoke(Node $node, CompilerContext $context): string|int|float|bool|array|null
public function __invoke(Node $node, CompilerContext $context): CompiledValue
{
if ($node instanceof Node\Stmt\Expression) {
return $this($node->expr, $context);
}

$constExprEvaluator = new ConstExprEvaluator(function (Node\Expr $node) use ($context): string|int|float|bool|array|null {
$constantName = null;

if (
$node instanceof Node\Expr\ConstFetch
&& ! in_array($node->name->toLowerString(), ['true', 'false', 'null'], true)
) {
$constantName = $this->resolveConstantName($node, $context);
} elseif ($node instanceof Node\Expr\ClassConstFetch) {
$constantName = $this->resolveClassConstantName($node, $context);
}

$constExprEvaluator = new ConstExprEvaluator(function (Node\Expr $node) use ($context, $constantName): string|int|float|bool|array|null {
if ($node instanceof Node\Expr\ConstFetch) {
return $this->compileConstFetch($node, $context);
return $this->getConstantValue($node, $constantName, $context);
}

if ($node instanceof Node\Expr\ClassConstFetch) {
return $this->compileClassConstFetch($node, $context);
return $this->getClassConstantValue($node, $constantName, $context);
}

if ($node instanceof Node\Scalar\MagicConst\Dir) {
Expand Down Expand Up @@ -99,85 +110,84 @@ public function __invoke(Node $node, CompilerContext $context): string|int|float
throw Exception\UnableToCompileNode::forUnRecognizedExpressionInContext($node, $context);
});

return $constExprEvaluator->evaluateDirectly($node);
$value = $constExprEvaluator->evaluateDirectly($node);

return new CompiledValue($value, $constantName);
}

/**
* Compile constant expressions
*
* @return scalar|array<scalar>|null
*
* @throws Exception\UnableToCompileNode
*/
private function compileConstFetch(Node\Expr\ConstFetch $constNode, CompilerContext $context): string|int|float|bool|array|null
private function resolveConstantName(Node\Expr\ConstFetch $constNode, CompilerContext $context): string
{
$constantName = $constNode->name->toString();

if ($context->getNamespace() !== null && $constNode->name->isUnqualified()) {
$namespacedConstantName = sprintf('%s\\%s', $context->getNamespace(), $constantName);

try {
return $this->getConstantValue($namespacedConstantName, $context);
} catch (IdentifierNotFound) {
// Try constant name without namespace
if ($this->constantExists($namespacedConstantName, $context)) {
return $namespacedConstantName;
}
}

if ($this->constantExists($constantName, $context)) {
return $constantName;
}

throw Exception\UnableToCompileNode::becauseOfNotFoundConstantReference($context, $constNode, $constantName);
}

private function constantExists(string $constantName, CompilerContext $context): bool
{
if (defined($constantName)) {
return true;
}

try {
return $this->getConstantValue($constantName, $context);
$context->getReflector()->reflectConstant($constantName);

return true;
} catch (IdentifierNotFound) {
throw Exception\UnableToCompileNode::becauseOfNotFoundConstantReference($context, $constNode, $constantName);
return false;
}
}

private function getConstantValue(string $constantName, CompilerContext $context): mixed
private function getConstantValue(Node\Expr\ConstFetch $node, ?string $constantName, CompilerContext $context): mixed
{
// It's not resolved when constant value is expression
$constantName ??= $this->resolveConstantName($node, $context);

if (defined($constantName)) {
return constant($constantName);
}

return $context->getReflector()->reflectConstant($constantName)->getValue();
}

/**
* Compile class constants
*
* @return scalar|array<scalar>|null
*
* @throws IdentifierNotFound
* @throws Exception\UnableToCompileNode If a referenced constant could not be located on the expected referenced class.
*/
private function compileClassConstFetch(Node\Expr\ClassConstFetch $node, CompilerContext $context): string|int|float|bool|array|null
private function resolveClassConstantName(Node\Expr\ClassConstFetch $node, CompilerContext $context): string
{
assert($node->name instanceof Node\Identifier);
$nodeName = $node->name->name;
$constantName = $node->name->name;
assert($node->class instanceof Node\Name);
$className = $node->class->toString();

if ($nodeName === 'class') {
return $this->resolveClassNameForClassNameConstant($className, $context);
}
return sprintf('%s::%s', $this->resolveClassName($className, $context), $constantName);
}

private function getClassConstantValue(Node\Expr\ClassConstFetch $node, ?string $classConstantName, CompilerContext $context): mixed
{
// It's not resolved when constant value is expression
$classConstantName ??= $this->resolveClassConstantName($node, $context);

$classInfo = null;
[$className, $constantName] = explode('::', $classConstantName);

if ($className === 'self' || $className === 'static') {
$classContext = $context->getClass();
assert($classContext !== null);
$classInfo = $classContext->hasConstant($nodeName) ? $classContext : null;
} elseif ($className === 'parent') {
$classContext = $context->getClass();
assert($classContext !== null);
$classInfo = $classContext->getParentClass();
if ($constantName === 'class') {
return $className;
}

if ($classInfo === null) {
$classInfo = $context->getReflector()->reflectClass($className);
}
$classReflection = $context->getReflector()->reflectClass($className);

$reflectionConstant = $classInfo->getReflectionConstant($nodeName);
$reflectionConstant = $classReflection->getReflectionConstant($constantName);

if (! $reflectionConstant instanceof ReflectionClassConstant) {
throw Exception\UnableToCompileNode::becauseOfNotFoundClassConstantReference($context, $classInfo, $node);
throw Exception\UnableToCompileNode::becauseOfNotFoundClassConstantReference($context, $classReflection, $node);
}

return $reflectionConstant->getValue();
Expand Down Expand Up @@ -207,22 +217,22 @@ private function compileClassConstant(CompilerContext $context): string
return $context->getClass()?->getName() ?? '';
}

private function resolveClassNameForClassNameConstant(string $className, CompilerContext $context): string
private function resolveClassName(string $className, CompilerContext $context): string
{
if ($className !== 'self' && $className !== 'static' && $className !== 'parent') {
return $className;
}

$classContext = $context->getClass();
assert($classContext !== null);

if ($className === 'self' || $className === 'static') {
if ($className !== 'parent') {
return $classContext->getName();
}

if ($className === 'parent') {
$parentClass = $classContext->getParentClass();
assert($parentClass instanceof ReflectionClass);

return $parentClass->getName();
}
$parentClass = $classContext->getParentClass();
assert($parentClass instanceof ReflectionClass);

return $className;
return $parentClass->getName();
}
}
18 changes: 18 additions & 0 deletions src/NodeCompiler/CompiledValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Roave\BetterReflection\NodeCompiler;

/**
* @internal
*/
class CompiledValue
{
/**
* @param scalar|array<scalar>|null $value
*/
public function __construct(public string|int|float|bool|array|null $value, public ?string $constantName = null)
{
}
}
21 changes: 8 additions & 13 deletions src/Reflection/ReflectionClassConstant.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PhpParser\Node\Stmt\ClassConst;
use ReflectionProperty;
use Roave\BetterReflection\NodeCompiler\CompiledValue;
use Roave\BetterReflection\NodeCompiler\CompileNodeToValue;
use Roave\BetterReflection\NodeCompiler\CompilerContext;
use Roave\BetterReflection\Reflection\StringCast\ReflectionClassConstantStringCast;
Expand All @@ -15,10 +16,7 @@

class ReflectionClassConstant
{
private bool $valueWasCached = false;

/** @var scalar|array<scalar>|null const value */
private string|int|float|bool|array|null $value = null;
private ?CompiledValue $compiledValue = null;

private Reflector $reflector;

Expand Down Expand Up @@ -71,17 +69,14 @@ public function getName(): string
*/
public function getValue(): string|int|float|bool|array|null
{
if ($this->valueWasCached !== false) {
return $this->value;
if ($this->compiledValue === null) {
$this->compiledValue = (new CompileNodeToValue())->__invoke(
$this->node->consts[$this->positionInNode]->value,
new CompilerContext($this->reflector, $this),
);
}

$this->value = (new CompileNodeToValue())->__invoke(
$this->node->consts[$this->positionInNode]->value,
new CompilerContext($this->reflector, $this),
);
$this->valueWasCached = true;

return $this->value;
return $this->compiledValue->value;
}

/**
Expand Down
15 changes: 6 additions & 9 deletions src/Reflection/ReflectionConstant.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PhpParser\Node;
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
use Roave\BetterReflection\BetterReflection;
use Roave\BetterReflection\NodeCompiler\CompiledValue;
use Roave\BetterReflection\NodeCompiler\CompileNodeToValue;
use Roave\BetterReflection\NodeCompiler\CompilerContext;
use Roave\BetterReflection\Reflection\Exception\InvalidConstantNode;
Expand Down Expand Up @@ -37,10 +38,7 @@ class ReflectionConstant implements Reflection

private ?int $positionInNode;

/** @var scalar|array<scalar>|null const value */
private string|int|float|bool|array|null $value = null;

private bool $valueWasCached = false;
private ?CompiledValue $compiledValue = null;

private function __construct()
{
Expand Down Expand Up @@ -206,22 +204,21 @@ public function isUserDefined(): bool
*/
public function getValue(): string|int|float|bool|array|null
{
if ($this->valueWasCached !== false) {
return $this->value;
if ($this->compiledValue !== null) {
return $this->compiledValue->value;
}

/** @psalm-suppress PossiblyNullArrayOffset */
$valueNode = $this->node instanceof Node\Expr\FuncCall
? $this->node->args[1]->value
: $this->node->consts[$this->positionInNode]->value;

$this->value = (new CompileNodeToValue())->__invoke(
$this->compiledValue = (new CompileNodeToValue())->__invoke(
$valueNode,
new CompilerContext($this->reflector, $this),
);
$this->valueWasCached = true;

return $this->value;
return $this->compiledValue->value;
}

public function getFileName(): ?string
Expand Down
Loading

0 comments on commit 165dee9

Please sign in to comment.