getkirby / kirby

Kirby's core application folder
https://getkirby.com
Other
1.32k stars 168 forks source link

Query options from associative array of scalars #5807

Open rasteiner opened 1 year ago

rasteiner commented 1 year ago

Description

It is unclear how to use an associative array of scalar values as query result for panel fields which accept options. Just using an associative array stores the "values" (the text which is presented to the user as "value" in the content file).

Expected behavior
Associative arrays of scalars should just be accepted as is and store the array key as value in the content file.

options:
  myValue: My text

should be doable like:

options:
  type: query
  query: site.method # returns ['myValue' => 'My text']

Otherwise, the docs should mention how to use associative arrays.

To reproduce

  1. Site method:
    Kirby::plugin('my/test', [
    'siteMethods' => [
        'method' => fn() => ['myValue' => 'My text']
    ],
    ];
  2. Blueprint
    fields:
    select:
    options:
      type: query
      query: site.method
  3. In the panel: select 'My text', save
  4. The content file will contain
    Select: My Text

Additional context
The scalar values of the associative array are transformed to a Kirby\Toolkit\Obj, with fields key and value. This allows us to use an associative array by explicitly telling kirby to use "text" {{ item.value }} and "value" {{ item.key }}, I didn't find any docs mentioning this however.

Contrary to OptionsApi, OptionsQuery doesn't have a default config for key and value, see https://github.com/getkirby/kirby/blob/4.0.0-beta.2/src/Option/OptionsQuery.php#L68

However, just changing the "array default" from "value, value" to "value, key" would break non associative arrays (it would start storing the numeric key). To work around this, the collection method could store string keys separately in the Obj

foreach ($array as $key => $value) {
    if (is_scalar($value) === true) {
        $fields = [
            'key'   => new Field(null, 'key', $key),
            'value' => new Field(null, 'value', $value),
        ];
        if(is_string($key)) {
            $fields['stringKey'] = new Field(null, 'stringKey', $key),
        }
        $array[$key] = new Obj($fields);
    }
}

and then a new default which looks for that:

$item instanceof Obj && $item->stringKey() => [
    'arrayItem',
    '{{ item.value }}',
    '{{ item.stringKey }}'
],
rasteiner commented 1 year ago

... or the whole "defaults and query parsing" steps of each item could be completely sidestepped for associative arrays when no text and value is present in the options. I heard you're going to drop php 8.0 support for Kirby 4, so you could use the new array_is_list function.



// convert result to a collection, or directly consume associative array
if (is_array($result) === true) {
    if(array_is_list($result) === false && $this->text === null && $this->value === null) {
        return Options::factory(array_map(
            fn($text, $value) => compact('text', 'value'),
            $result,
            array_keys($result)
        ));
    } else {
        $result = $this->collection($result);
    }
}