2020-08-30 20:27:43 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Phabel\Plugin;
|
|
|
|
|
|
|
|
use Phabel\Plugin;
|
|
|
|
use PhpParser\Node;
|
|
|
|
use PhpParser\Node\Expr;
|
|
|
|
use PhpParser\Node\Expr\ArrayDimFetch;
|
|
|
|
use PhpParser\Node\Expr\ClassConstFetch;
|
|
|
|
use PhpParser\Node\Expr\Isset_;
|
|
|
|
use PhpParser\Node\Expr\PropertyFetch;
|
|
|
|
use PhpParser\Node\Expr\StaticPropertyFetch;
|
|
|
|
use PhpParser\Node\Expr\Ternary;
|
|
|
|
use PhpParser\Node\Scalar\LNumber;
|
|
|
|
use PhpParser\Node\Scalar\String_;
|
|
|
|
use PhpParser\Node\VarLikeIdentifier;
|
|
|
|
use ReflectionClass;
|
|
|
|
use ReflectionClassConstant;
|
|
|
|
use ReflectionException;
|
|
|
|
|
2020-09-05 22:35:30 +02:00
|
|
|
/**
|
|
|
|
* Replace nested expressions in isset.
|
|
|
|
*
|
|
|
|
* @author Daniil Gentili <daniil@daniil.it>
|
|
|
|
* @license MIT
|
|
|
|
*/
|
2020-08-30 20:27:43 +02:00
|
|
|
class IssetExpressionFixer extends Plugin
|
|
|
|
{
|
|
|
|
/**
|
2020-09-05 22:35:30 +02:00
|
|
|
* Recursively extract bottom ArrayDimFetch.
|
2020-08-30 20:27:43 +02:00
|
|
|
*
|
|
|
|
* @param Node $var
|
|
|
|
* @return Node
|
|
|
|
*/
|
|
|
|
private static function &extractWorkVar(Node &$var): Node
|
|
|
|
{
|
|
|
|
if ($var instanceof ArrayDimFetch && $var->var instanceof ArrayDimFetch) {
|
|
|
|
return self::extractWorkVar($var->var);
|
|
|
|
}
|
|
|
|
return $var;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Wrap boolean isset check.
|
|
|
|
*
|
|
|
|
* @param Expr $node Node
|
|
|
|
*
|
|
|
|
* @return ArrayDimFetch
|
|
|
|
*/
|
|
|
|
private static function wrapBoolean(Expr $node): ArrayDimFetch
|
|
|
|
{
|
|
|
|
return new ArrayDimFetch(
|
|
|
|
self::callPoly(
|
|
|
|
'returnMe',
|
|
|
|
new Ternary(
|
|
|
|
$node,
|
|
|
|
self::toLiteral([0]),
|
|
|
|
self::toLiteral([]),
|
|
|
|
)
|
|
|
|
),
|
|
|
|
new LNumber(0)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function enter(Isset_ $isset): void
|
|
|
|
{
|
|
|
|
foreach ($isset->vars as $key => &$var) {
|
|
|
|
/** @var array<string, array<class-string<Expr>, true>> */
|
2020-09-01 14:31:23 +02:00
|
|
|
$subNodes = $this->getConfig(\get_class($var), false);
|
2020-08-30 20:27:43 +02:00
|
|
|
if (!$subNodes) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$workVar = $this->extractWorkVar($var);
|
|
|
|
$needsFixing = false;
|
|
|
|
foreach ($subNodes as $key => $types) {
|
|
|
|
if (isset($types[\get_class($workVar->{$key})])) {
|
|
|
|
$needsFixing = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!$needsFixing) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-08-30 20:55:28 +02:00
|
|
|
switch ($class = \get_class($workVar)) {
|
2020-08-30 20:27:43 +02:00
|
|
|
case ArrayDimFetch::class:
|
|
|
|
case PropertyFetch::class:
|
|
|
|
$workVar->var = self::callPoly('returnMe', $workVar->var);
|
|
|
|
break;
|
|
|
|
case StaticPropertyFetch::class:
|
|
|
|
$workVar = $this->wrapBoolean(self::callPoly(
|
|
|
|
'staticExists',
|
|
|
|
$workVar->class,
|
|
|
|
$workVar->name instanceof VarLikeIdentifier ? new String_($workVar->name->name) : $workVar->name,
|
|
|
|
new LNumber(1)
|
|
|
|
));
|
|
|
|
break;
|
|
|
|
case ClassConstFetch::class:
|
|
|
|
$workVar = $this->wrapBoolean(self::callPoly(
|
|
|
|
'staticExists',
|
|
|
|
$workVar->class,
|
|
|
|
new String_($workVar->name->name),
|
|
|
|
new LNumber(0)
|
|
|
|
));
|
|
|
|
break;
|
2020-08-30 20:55:28 +02:00
|
|
|
default:
|
|
|
|
throw new \RuntimeException("Trying to fix unknown isset expression $class");
|
2020-08-30 20:27:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the data provided.
|
|
|
|
*
|
|
|
|
* @param mixed $data Data
|
|
|
|
*
|
|
|
|
* @return mixed
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
*
|
|
|
|
* @psalm-param T $data data
|
|
|
|
*
|
|
|
|
* @psalm-return T
|
|
|
|
*/
|
|
|
|
public static function returnMe($data)
|
|
|
|
{
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get name of class.
|
|
|
|
*
|
|
|
|
* @param class-string|object $class Class
|
|
|
|
*
|
|
|
|
* @return class-string
|
|
|
|
*/
|
|
|
|
public static function getClass($class): string
|
|
|
|
{
|
|
|
|
return \is_string($class) ? $class : \get_class($class);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-09-05 22:35:30 +02:00
|
|
|
* Check if static property is set.
|
2020-08-30 20:27:43 +02:00
|
|
|
*
|
|
|
|
* @param class-string|object $class Class
|
|
|
|
* @param string $property Property name
|
|
|
|
* @param boolean $propertyOrConstant Whether to fetch the property or the constant
|
2020-09-05 22:35:30 +02:00
|
|
|
*
|
2020-08-30 20:27:43 +02:00
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public static function staticExists($class, string $property, bool $propertyOrConstant): bool
|
|
|
|
{
|
|
|
|
$reflectionClass = new ReflectionClass($class);
|
2020-09-05 22:35:30 +02:00
|
|
|
$class = self::getClass($class);
|
2020-08-30 20:27:43 +02:00
|
|
|
if ($propertyOrConstant) {
|
|
|
|
try {
|
|
|
|
$reflection = $reflectionClass->getProperty($property);
|
|
|
|
} catch (ReflectionException $e) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-09-05 22:35:30 +02:00
|
|
|
} elseif (PHP_VERSION_ID >= 70100) {
|
2020-08-30 20:27:43 +02:00
|
|
|
try {
|
|
|
|
$reflection = new ReflectionClassConstant($class, $property);
|
|
|
|
} catch (ReflectionException $e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
2020-09-05 22:35:30 +02:00
|
|
|
return isset($reflectionClass->getConstants()[$property]);
|
2020-08-30 20:27:43 +02:00
|
|
|
}
|
|
|
|
|
2020-09-05 22:35:30 +02:00
|
|
|
$classCaller = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['class'] ?? '';
|
2020-08-30 20:27:43 +02:00
|
|
|
$allowProtected = false;
|
|
|
|
$allowPrivate = false;
|
|
|
|
if ($classCaller) {
|
|
|
|
if ($class === $classCaller) {
|
|
|
|
$allowProtected = $allowPrivate = true;
|
2020-09-05 22:35:30 +02:00
|
|
|
} elseif ($reflectionClass->isSubclassOf($classCaller) || (new ReflectionClass($classCaller))->isSubclassOf($class)) {
|
2020-08-30 20:27:43 +02:00
|
|
|
$allowProtected = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($reflection->isPrivate()) {
|
|
|
|
return $allowPrivate ? $reflection->getValue() !== null : false;
|
|
|
|
}
|
|
|
|
if ($reflection->isProtected()) {
|
|
|
|
return $allowProtected ? $reflection->getValue() !== null : false;
|
|
|
|
}
|
|
|
|
return $reflection->getValue() !== null;
|
|
|
|
}
|
|
|
|
}
|