diff --git a/src/Core/Parser/SyntaxTree/Expression/NullcoalescingExpressionNode.php b/src/Core/Parser/SyntaxTree/Expression/NullcoalescingExpressionNode.php new file mode 100644 index 000000000..6fe253301 --- /dev/null +++ b/src/Core/Parser/SyntaxTree/Expression/NullcoalescingExpressionNode.php @@ -0,0 +1,85 @@ +\%\s\{\}\:\,]+ # Check variable side + [\s]?\?\?[\s]? + [_a-zA-Z0-9.\s\'\"\\.]+ # Fallback value side + ) + } # End of shorthand syntax + )/x'; + + /** + * Filter out variable names form expression + */ + protected static $variableDetection = '/[^\'_a-zA-Z0-9\.\\\\]{0,1}([_a-zA-Z0-9\.\\\\]*)[^\']{0,1}/'; + + /** + * @param RenderingContextInterface $renderingContext + * @param string $expression + * @param array $matches + * @return mixed + */ + public static function evaluateExpression(RenderingContextInterface $renderingContext, $expression, array $matches) + { + $parts = preg_split('/([\?\?])/s', $expression); + $parts = array_map([__CLASS__, 'trimPart'], $parts); + + foreach($parts as $part) { + $value = static::getTemplateVariableOrValueItself($part, $renderingContext); + if(!is_null($value)) { + return $value; + } + } + + return null; + } + + + /** + * @param mixed $candidate + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + protected static function getTemplateVariableOrValueItself($candidate, RenderingContextInterface $renderingContext) + { + $variables = $renderingContext->getVariableProvider()->getAll(); + $extractor = new VariableExtractor(); + $suspect = $extractor->getByPath($variables, $candidate); + + if (is_numeric($candidate)) { + $suspect = $candidate; + } elseif (mb_strpos($candidate, '\'') === 0) { + $suspect = trim($candidate, '\''); + } elseif (mb_strpos($candidate, '"') === 0) { + $suspect = trim($candidate, '"'); + } + + return $suspect; + } +} diff --git a/src/Core/Rendering/RenderingContext.php b/src/Core/Rendering/RenderingContext.php index 43fb7908a..4793a68de 100644 --- a/src/Core/Rendering/RenderingContext.php +++ b/src/Core/Rendering/RenderingContext.php @@ -14,6 +14,7 @@ use TYPO3Fluid\Fluid\Core\Parser\Interceptor\Escape; use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\Expression\CastingExpressionNode; use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\Expression\MathExpressionNode; +use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\Expression\NullcoalescingExpressionNode; use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\Expression\TernaryExpressionNode; use TYPO3Fluid\Fluid\Core\Parser\TemplateParser; use TYPO3Fluid\Fluid\Core\Parser\TemplateProcessor\EscapingModifierTemplateProcessor; @@ -106,6 +107,7 @@ class RenderingContext implements RenderingContextInterface protected $expressionNodeTypes = [ CastingExpressionNode::class, MathExpressionNode::class, + NullcoalescingExpressionNode::class, TernaryExpressionNode::class, ]; diff --git a/tests/Unit/Core/Parser/SyntaxTree/Expression/NullcoalescingExpressionNodeTest.php b/tests/Unit/Core/Parser/SyntaxTree/Expression/NullcoalescingExpressionNodeTest.php new file mode 100644 index 000000000..a7aa80d02 --- /dev/null +++ b/tests/Unit/Core/Parser/SyntaxTree/Expression/NullcoalescingExpressionNodeTest.php @@ -0,0 +1,59 @@ +setVariableProvider(new StandardVariableProvider($variables)); + $result = NullcoalescingExpressionNode::evaluateExpression($renderingContext, $expression, []); + $this->assertEquals($expected, $result); + } + + /** + * @return array + */ + public function getEvaluateExpressionTestValues() + { + return [ + ['{a ?? 1}', ['a' => 'a'], 'a'], + ['{a ?? 1}', ['a' => null], 1], + ['{a ?? b}', ['a' => 'a', 'b' => 'b'], 'a'], + ['{a ?? b}', ['a' => null, 'b' => 'b'], 'b'], + ['{a ?? b ?? c}', ['a' => 1, 'b' => 2, 'c' => 3], 1], + ['{a ?? b ?? c}', ['a' => 1, 'b' => null, 'c' => 3], 1], + ['{a ?? b ?? c}', ['a' => null, 'b' => 2, 'c' => 3], 2], + ['{a ?? b ?? c}', ['a' => null, 'b' => null, 'c' => 3], 3], + ['{a ?? b ?? c}', ['a' => 'd', 'b' => 'e', 'c' => 'f'], 'd'], + ['{a ?? b ?? c}', ['a' => 'd', 'b' => null, 'c' => 'f'], 'd'], + ['{a ?? b ?? c}', ['a' => null, 'b' => 'e', 'c' => 'f'], 'e'], + ['{a ?? b ?? c}', ['a' => null, 'b' => null, 'c' => 'f'], 'f'], + ['{a ?? b ?? c}', ['a' => null, 'b' => null, 'c' => null], null], + ['{a ?? b ?? \'test\'}', ['a' => null, 'b' => null], 'test'], + ['{a ?? b ?? "test"}', ['a' => null, 'b' => null], 'test'], + + ]; + } +}