Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 48 additions & 6 deletions src/Fields/Fieldtype.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

use Facades\Statamic\Fields\FieldtypeRepository;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Collection;
use Statamic\Extend\HasHandle;
use Statamic\Extend\RegistersItself;
use Statamic\Facades\Blink;
use Statamic\Facades\GraphQL;
use Statamic\Query\Scopes\Filters\Fields\FieldtypeFilter;
use Statamic\Statamic;
use Statamic\Support\Str;
use WeakMap;

use function Statamic\trans as __;

Expand Down Expand Up @@ -38,6 +40,7 @@ abstract class Fieldtype implements Arrayable
protected $defaultValue;
protected $configFields = [];
protected static $extraConfigFields = [];
private static ?WeakMap $configDefaults = null;
protected $icon;

public static function title()
Expand Down Expand Up @@ -378,15 +381,54 @@ public function config(?string $key = null, $fallback = null)
}

$fieldConfig = $this->field->config();
$fields = $this->configFields()->all();

$config = $this->configFields()->all()
->reject(fn ($field, $handle) => array_key_exists($handle, $fieldConfig))
->map->defaultValue()
->merge($fieldConfig);
// Most calls ask for a single key, so answer those without building the whole config.
if (is_string($key) && $key !== '') {
if (array_key_exists($key, $fieldConfig)) {
return $fieldConfig[$key] ?? $fallback;
}

return ($fields->has($key) ? self::configDefault($fields, $key) : null) ?? $fallback;
}

$config = [];

foreach ($fields as $handle => $field) {
if (! array_key_exists($handle, $fieldConfig)) {
$config[$handle] = self::configDefault($fields, $handle);
}
}

$config = array_merge($config, $fieldConfig);

return $key
? ($config->get($key) ?? $fallback)
: $config->all();
? ($config[$key] ?? $fallback)
: $config;
}

/**
* A config field's default value. It's worked out once per config fields
* collection, which Blink keeps for the request. Computed defaults are
* still resolved on every read.
*/
private static function configDefault(Collection $fields, string $handle)
{
$field = $fields->get($handle);

if ($field->hasComputedDefault()) {
return $field->defaultValue();
}

self::$configDefaults ??= new WeakMap;
$defaults = self::$configDefaults[$fields] ?? [];

if (! array_key_exists($handle, $defaults)) {
$defaults[$handle] = $field->defaultValue();
self::$configDefaults[$fields] = $defaults;
}

return $defaults[$handle];
}

public function preload()
Expand Down
8 changes: 6 additions & 2 deletions src/Fieldtypes/HasSelectOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,13 @@ private function getLabel($actualValue)
$value = $this->castFromBoolean($value);
}

$option = collect($this->getOptions())->filter(fn ($option) => $option['value'] === $value)->first();
foreach ($this->getOptions() as $option) {
if ($option['value'] === $value) {
return $option['label'] ?? $option['value'];
}
}

return $option ? ($option['label'] ?? $option['value']) : $actualValue;
return $actualValue;
}

private function castToBoolean($value)
Expand Down
49 changes: 49 additions & 0 deletions tests/Fields/FieldtypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Facades\Blink;
use Statamic\Facades\Field as FieldFacade;
use Statamic\Fields\Blueprint;
use Statamic\Fields\ConfigFields;
use Statamic\Fields\Field;
Expand Down Expand Up @@ -793,6 +795,43 @@ public function it_only_computes_default_values_for_config_fields_missing_from_t
$this->assertSame(1, FieldtypeWithCountedDefaultValue::$timesDefaultValueWasComputed);
}

#[Test]
public function config_field_default_values_are_only_computed_once_per_request()
{
FieldtypeWithCountedDefaultValue::$timesDefaultValueWasComputed = 0;

(new FieldtypeWithCountedDefaultValue)::register();

foreach (range(1, 3) as $i) {
$fieldtype = (new TestFieldtypeWithCountedConfigField)->setField(new Field('test', []));

$this->assertEquals('default!', $fieldtype->config('alfa'));
$this->assertEquals(['alfa' => 'default!'], $fieldtype->config());
}

$this->assertSame(1, FieldtypeWithCountedDefaultValue::$timesDefaultValueWasComputed);

Blink::flush();

$this->assertEquals('default!', (new TestFieldtypeWithCountedConfigField)->setField(new Field('test', []))->config('alfa'));
$this->assertSame(2, FieldtypeWithCountedDefaultValue::$timesDefaultValueWasComputed);
}

#[Test]
public function computed_config_field_defaults_are_resolved_on_every_read()
{
$count = 0;
FieldFacade::computedDefault('counted', function () use (&$count) {
return 'computed '.++$count;
});

$fieldtype = (new TestFieldtypeWithComputedConfigField)->setField(new Field('test', []));

$this->assertEquals('computed 1', $fieldtype->config('alfa'));
$this->assertEquals('computed 2', $fieldtype->config('alfa'));
$this->assertEquals(['alfa' => 'computed 3'], $fieldtype->config());
}

#[Test]
#[Group('graphql')]
public function it_gets_the_graphql_type_of_string_by_default()
Expand Down Expand Up @@ -1002,6 +1041,16 @@ class TestFieldtypeWithCountedConfigField extends Fieldtype
];
}

class TestFieldtypeWithComputedConfigField extends Fieldtype
{
protected $configFields = [
'alfa' => [
'type' => 'text',
'default' => 'computed:counted',
],
];
}

class FieldtypeWithCountedDefaultValue extends Fieldtype
{
protected static $handle = 'counted_default';
Expand Down
Loading