diff --git a/composer.json b/composer.json index 17fbe5899..50ba3d3fe 100644 --- a/composer.json +++ b/composer.json @@ -49,14 +49,14 @@ "require-dev": { "maglnet/composer-require-checker": "^4.7.1", "phpunit/phpunit": "^10.5.45", - "rector/rector": "^2.0.9", + "rector/rector": "^2.0.10", "roave/infection-static-analysis-plugin": "^1.35", "spatie/phpunit-watcher": "^1.24", "vimeo/psalm": "^5.26.1 || ^6.8.8", "yiisoft/di": "^1.3", "yiisoft/event-dispatcher": "^1.1", "yiisoft/log": "^2.1", - "yiisoft/router-fastroute": "^4.0", + "yiisoft/router-fastroute": "^4.0.1", "yiisoft/test-support": "^3.0.2", "yiisoft/translator-message-php": "^1.1.1" }, diff --git a/src/DetailView.php b/src/DetailView.php index 53514e741..044856e0b 100644 --- a/src/DetailView.php +++ b/src/DetailView.php @@ -7,13 +7,14 @@ use Closure; use InvalidArgumentException; use JsonException; +use Stringable; use Yiisoft\Html\Html; use Yiisoft\Widget\Widget; use Yiisoft\Yii\DataView\Field\DataField; +use function array_key_exists; use function is_array; use function is_bool; -use function is_object; /** * `DetailView` displays details about a single data item. @@ -298,11 +299,6 @@ public function render(): string ); } - private function has(string $attribute): bool - { - return is_array($this->data) ? array_key_exists($attribute, $this->data) : isset($this->data->$attribute); - } - /** * @psalm-return list< * array{ @@ -338,7 +334,7 @@ private function normalizeColumns(array $fields): array 'labelTag' => $labelTag, 'value' => $field->encodeValue ? Html::encodeAttribute($this->renderValue($field->name, $field->value)) - : (string) $this->renderValue($field->name, $field->value) + : $this->renderValue($field->name, $field->value) , 'valueAttributes' => $this->renderAttributes($valueAttributes), 'valueTag' => $valueTag, @@ -397,31 +393,46 @@ private function renderFields(): string return implode("\n", $rows); } - private function renderValue(string $attribute, mixed $value): mixed + private function renderValue(string $property, string|Stringable|int|float|Closure|null $value): string { if ($this->data === []) { throw new InvalidArgumentException('The "data" must be set.'); } - if ($value === null && is_array($this->data) && $this->has($attribute)) { - return match (is_bool($this->data[$attribute])) { - true => $this->data[$attribute] ? $this->valueTrue : $this->valueFalse, - default => $this->data[$attribute], - }; - } - - if ($value === null && is_object($this->data) && $this->has($attribute)) { - return match (is_bool($this->data->{$attribute})) { - true => $this->data->{$attribute} ? $this->valueTrue : $this->valueFalse, - default => $this->data->{$attribute}, - }; + if ($value === null) { + return $this->extractValueFromData($property); } if ($value instanceof Closure) { + /** + * @psalm-var Closure(array|object): string $value + */ return $value($this->data); } - return $value; + return (string) $value; + } + + private function extractValueFromData(string $property): string + { + if (is_array($this->data)) { + if (array_key_exists($property, $this->data)) { + return (string) match (is_bool($this->data[$property])) { + true => $this->data[$property] ? $this->valueTrue : $this->valueFalse, + default => $this->data[$property], + }; + } + return ''; + } + + if (isset($this->data->$property)) { + return (string) match (is_bool($this->data->{$property})) { + true => $this->data->{$property} ? $this->valueTrue : $this->valueFalse, + default => $this->data->{$property}, + }; + } + + return ''; } /** diff --git a/src/Field/DataField.php b/src/Field/DataField.php index 86559a388..2f1ce7e25 100644 --- a/src/Field/DataField.php +++ b/src/Field/DataField.php @@ -5,6 +5,7 @@ namespace Yiisoft\Yii\DataView\Field; use Closure; +use Stringable; /** * `DataField` represents a field configuration for {@see DetailView} widget. @@ -30,7 +31,10 @@ final class DataField * @param string $labelTag Label HTML tag. * Example: 'span', 'div', 'label' * - * @param mixed|null $value Explicit value. If `null`, the value is obtained from the data by field `$name`. + * @param Closure|float|int|string|Stringable|null $value The field value. It can be: + * - `null` if the value should be retrieved from the data object using the property name; + * - a closure that will be called to get the value, format: `function (array|object $data): string`; + * - string, `Stringable`, integer or float which will be used as is. * * @param string $valueTag Value HTML tag. * Example: 'span', 'div', 'p' @@ -41,13 +45,16 @@ final class DataField * Example closure: `fn($data) => ['class' => $data->status . '-value']` * * @param bool $encodeValue Whether the value is HTML encoded + * + * @template TData as array|object + * @psalm-param string|Stringable|int|float|(Closure(TData): string)|null $value */ public function __construct( public readonly string $name = '', public readonly string $label = '', public readonly array|Closure $labelAttributes = [], public readonly string $labelTag = '', - public readonly mixed $value = null, + public readonly string|Stringable|int|float|Closure|null $value = null, public readonly string $valueTag = '', public readonly array|Closure $valueAttributes = [], public readonly bool $encodeValue = true,