Skip to content

Commit

Permalink
[5.x] Add Boolable interface and fix ArrayableStrings in Antlers cond…
Browse files Browse the repository at this point in the history
…itions (#10595)

Co-authored-by: Jason Varga <[email protected]>
  • Loading branch information
JohnathonKoster and jasonvarga authored Aug 8, 2024
1 parent dff7005 commit 6e93d96
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 3 deletions.
8 changes: 8 additions & 0 deletions src/Contracts/Support/Boolable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Statamic\Contracts\Support;

interface Boolable
{
public function toBool(): bool;
}
8 changes: 7 additions & 1 deletion src/Fields/ArrayableString.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

use Illuminate\Contracts\Support\Arrayable;
use JsonSerializable;
use Statamic\Contracts\Support\Boolable;

class ArrayableString implements Arrayable, JsonSerializable
class ArrayableString implements Arrayable, Boolable, JsonSerializable
{
protected $value;
protected $extra;
Expand All @@ -26,6 +27,11 @@ public function extra()
return (array) $this->extra;
}

public function toBool(): bool
{
return (bool) $this->value;
}

public function __toString()
{
return (string) $this->value ?? '';
Expand Down
32 changes: 32 additions & 0 deletions src/View/Antlers/Language/Runtime/PathDataManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Illuminate\Support\Str;
use Statamic\Contracts\Data\Augmentable;
use Statamic\Contracts\Query\Builder;
use Statamic\Contracts\Support\Boolable;
use Statamic\Contracts\View\Antlers\Parser;
use Statamic\Fields\ArrayableString;
use Statamic\Fields\Value;
Expand Down Expand Up @@ -89,6 +90,8 @@ class PathDataManager
*/
private $reduceFinal = true;

private $isReturningForConditions = false;

/**
* @var Parser|null
*/
Expand Down Expand Up @@ -165,6 +168,17 @@ private function unlockData()
}
}

/**
* Reset state for things that should not persist
* across repeated calls to getData and friends.
*
* @return void
*/
private function resetInternalState()
{
$this->isReturningForConditions = false;
}

/**
* Sets the internal environment reference.
*
Expand Down Expand Up @@ -300,6 +314,13 @@ public function setReduceFinal($reduceFinal)
return $this;
}

public function setIsReturningForConditions($isCondition)
{
$this->isReturningForConditions = $isCondition;

return $this;
}

public function getReduceFinal()
{
return $this->reduceFinal;
Expand Down Expand Up @@ -523,6 +544,8 @@ public function getData(VariableReference $path, $data, $isForArrayIndex = false
$path = $tempPathParser->parse($dynamicPath);

if (! $this->guardRuntimeAccess($path->normalizedReference)) {
$this->resetInternalState();

return null;
}
}
Expand Down Expand Up @@ -555,6 +578,8 @@ public function getData(VariableReference $path, $data, $isForArrayIndex = false
}

if ($pathItem->name == 'void' && count($path->pathParts) == 1) {
$this->resetInternalState();

return 'void::'.GlobalRuntimeState::$environmentId;
}

Expand Down Expand Up @@ -750,6 +775,7 @@ public function getData(VariableReference $path, $data, $isForArrayIndex = false
}

$this->namedSlotsInScope = false;
$this->resetInternalState();

return $this->reducedVar;
}
Expand Down Expand Up @@ -889,6 +915,12 @@ private function compact($isFinal)
return;
}

if ($this->isReturningForConditions && $isFinal && $this->reducedVar instanceof Boolable) {
$this->reducedVar = $this->reducedVar->toBool();

return;
}

if ($this->antlersParser == null) {
$this->reducedVar = self::reduce($this->reducedVar, $this->isPair, $this->shouldDoValueIntercept);
} else {
Expand Down
7 changes: 5 additions & 2 deletions src/View/Antlers/Language/Runtime/Sandbox/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\Support\MessageBag;
use Illuminate\Support\ViewErrorBag;
use Statamic\Contracts\Query\Builder;
use Statamic\Contracts\Support\Boolable;
use Statamic\Contracts\View\Antlers\Parser;
use Statamic\Fields\ArrayableString;
use Statamic\Fields\Value;
Expand Down Expand Up @@ -353,8 +354,8 @@ public function evaluateBool($nodes)
$this->isEvaluatingTruthValue = false;

if (is_object($result)) {
if ($result instanceof ArrayableString) {
$value = $this->getTruthValue($result->value());
if ($result instanceof Boolable) {
$value = $this->getTruthValue($result->toBool());
$this->unlock();

return $value;
Expand Down Expand Up @@ -1255,6 +1256,8 @@ private function scopeValue($name, $originalNode = null)
if ($name instanceof VariableReference) {
if (! $this->isEvaluatingTruthValue) {
$this->dataRetriever->setReduceFinal(false);
} else {
$this->dataRetriever->setIsReturningForConditions(true);
}

if ($originalNode != null && $originalNode->hasModifiers()) {
Expand Down
51 changes: 51 additions & 0 deletions tests/Antlers/Runtime/TemplateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
use PHPUnit\Framework\Attributes\Test;
use Statamic\Contracts\Data\Augmentable;
use Statamic\Contracts\Query\Builder;
use Statamic\Contracts\Support\Boolable;
use Statamic\Data\HasAugmentedData;
use Statamic\Facades\Entry;
use Statamic\Fields\ArrayableString;
use Statamic\Fields\Blueprint;
use Statamic\Fields\Field;
use Statamic\Fields\Fieldtype;
use Statamic\Fields\LabeledValue;
use Statamic\Fields\Value;
use Statamic\Fields\Values;
use Statamic\Tags\Tags;
Expand Down Expand Up @@ -464,6 +466,55 @@ public function ternary_condition_inside_parameter()
));
}

#[Test]
#[DataProvider('boolablesInTernaryProvider')]
public function ternary_condition_with_boolables_supplied_to_tags_resolve_correctly($value, $expected)
{
$this->withFakeViews();

$this->viewShouldReturnRaw('test', "{{ the_field ? 'true' : 'false' }}");

$template = <<<'EOT'
view: {{ the_field ? 'true' : 'false' }}, partial: {{ partial:test :the_field="the_field" }}
EOT;

$this->assertSame($expected, $this->renderString($template, ['the_field' => $value], true));
}

public static function boolablesInTernaryProvider()
{
return [
'truthy generic boolable' => [
new class implements Boolable
{
public function toBool(): bool
{
return true;
}
},
'view: true, partial: true',
],
'falsey generic boolable' => [
new class implements Boolable
{
public function toBool(): bool
{
return false;
}
},
'view: false, partial: false',
],
'truthy LabeledValue' => [
new LabeledValue('foo', 'Foo'),
'view: true, partial: true',
],
'falsey LabeledValue' => [
new LabeledValue(null, null),
'view: false, partial: false',
],
];
}

#[Test]
public function null_coalescence()
{
Expand Down
9 changes: 9 additions & 0 deletions tests/Fields/ArrayableStringTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,13 @@ public function it_converts_to_json()

$this->assertSame(json_encode($val->toArray()), json_encode($val));
}

#[Test]
public function it_converts_to_bool()
{
$this->assertTrue((new ArrayableString('world'))->toBool());
$this->assertFalse((new ArrayableString(null))->toBool());
$this->assertTrue((new ArrayableString(4))->toBool());
$this->assertFalse((new ArrayableString(''))->toBool());
}
}

0 comments on commit 6e93d96

Please sign in to comment.