assignAndBinaryMap = $assignAndBinaryMap; } /** * Tries to match left or right parts (xor), * returns null or match on first condition and then second condition. No matter what the origin order is. * * @param callable(Node $firstNode, Node $secondNode): bool|class-string $firstCondition * @param callable(Node $firstNode, Node $secondNode): bool|class-string $secondCondition */ public function matchFirstAndSecondConditionNode(BinaryOp $binaryOp, $firstCondition, $secondCondition) : ?TwoNodeMatch { $this->validateCondition($firstCondition); $this->validateCondition($secondCondition); $firstCondition = $this->normalizeCondition($firstCondition); $secondCondition = $this->normalizeCondition($secondCondition); if ($firstCondition($binaryOp->left, $binaryOp->right) && $secondCondition($binaryOp->right, $binaryOp->left)) { return new TwoNodeMatch($binaryOp->left, $binaryOp->right); } if (!$firstCondition($binaryOp->right, $binaryOp->left)) { return null; } if (!$secondCondition($binaryOp->left, $binaryOp->right)) { return null; } return new TwoNodeMatch($binaryOp->right, $binaryOp->left); } public function inverseBooleanOr(BooleanOr $booleanOr) : ?BinaryOp { // no nesting if ($booleanOr->left instanceof BooleanOr) { return null; } if ($booleanOr->right instanceof BooleanOr) { return null; } $inversedNodeClass = $this->resolveInversedNodeClass($booleanOr); if ($inversedNodeClass === null) { return null; } $firstInversedExpr = $this->inverseNode($booleanOr->left); $secondInversedExpr = $this->inverseNode($booleanOr->right); return new $inversedNodeClass($firstInversedExpr, $secondInversedExpr); } public function invertCondition(BinaryOp $binaryOp) : ?BinaryOp { // no nesting if ($binaryOp->left instanceof BooleanOr) { return null; } if ($binaryOp->right instanceof BooleanOr) { return null; } $inversedNodeClass = $this->resolveInversedNodeClass($binaryOp); if ($inversedNodeClass === null) { return null; } return new $inversedNodeClass($binaryOp->left, $binaryOp->right); } /** * @return \PhpParser\Node\Expr\BinaryOp|\PhpParser\Node\Expr|\PhpParser\Node\Expr\BooleanNot */ public function inverseNode(Expr $expr) { if ($expr instanceof BinaryOp) { $inversedBinaryOp = $this->assignAndBinaryMap->getInversed($expr); if ($inversedBinaryOp !== null) { return new $inversedBinaryOp($expr->left, $expr->right); } } if ($expr instanceof BooleanNot) { return $expr->expr; } return new BooleanNot($expr); } /** * @param callable(Node $firstNode, Node $secondNode): bool|class-string $firstCondition */ private function validateCondition($firstCondition) : void { if (\is_callable($firstCondition)) { return; } if (\is_a($firstCondition, Node::class, \true)) { return; } throw new ShouldNotHappenException(); } /** * @param callable(Node $firstNode, Node $secondNode): bool|class-string $condition * @return callable(Node $firstNode, Node $secondNode): bool */ private function normalizeCondition($condition) : callable { if (\is_callable($condition)) { return $condition; } return static function (Node $node) use($condition) : bool { return $node instanceof $condition; }; } /** * @return class-string|null */ private function resolveInversedNodeClass(BinaryOp $binaryOp) : ?string { $inversedNodeClass = $this->assignAndBinaryMap->getInversed($binaryOp); if ($inversedNodeClass !== null) { return $inversedNodeClass; } if ($binaryOp instanceof BooleanOr) { return BooleanAnd::class; } return null; } }