getkirby / ideas

This is the backlog of ideas and feature requests from the last two years. Use our new feedback platform to post your new ideas or vote on existing ideas.
https://feedback.getkirby.com
20 stars 0 forks source link

Structure field: `blueprint()` method for `StructureObject` #358

Open hdodov opened 5 years ago

hdodov commented 5 years ago

I'm writing a Walker class that allows you to recursively walk over a Model's fields, resolving structures in the process. The Site, Page, and File classes all have a blueprint() method, but entries in a Structure (StructureObject instances) don't.

I get that this is probably due to structures not having their own blueprint file, but it still makes sense to have a blueprint() method. After all, a structure entry is like a nested Model.

Let's say I have this page blueprint:

title: Page
fields:
  text:
    type: textarea
    label: Content
  child:
    type: pages
    multiple: false
  items:
    type: structure
    fields:
      icon:
        type: files
        multiple: false
      name:
        type: text

I would be great if:

$page->blueprint()->fields(); // [text, child, items]
$page->child()->toPage()->blueprint()->fields(); // [...]
$page->items()->toStructure()->blueprint()->fields(); // [icon, name]

foreach ($page->items()->toStructure() as $entry) {
    $entry->blueprint(); // [icon, name]
}

It makes sense to have blueprints for Structure and StructureObject classes. Also, notice how the pages and structure fields would have identical APIs.

texnixe commented 5 years ago

how page fields

The page field does not exists, the field is called pages.

foreach ($page->items()->toStructure() as $entry) { $entry->blueprint(); // [icon, name] }

IMO, that loop doesn't make sense, it would return the same stuff over and over again, because a structure field always has the same fields.

hdodov commented 5 years ago

Oops, yes, edited my comment above to pages.

IMO, that loop doesn't make sense, it would return the same stuff over and over again, because a structure field always has the same fields.

That's my point kind of. If you pass $entry in other functions, you can call $entry->blueprint()->fields() and know what data to expect in that structure entry. Otherwise, you need to store the page blueprints, extract the structure blueprint's fields and pass it along with the entry:

$fieldsBlueprint = $page->blueprint()->fields();
$structureEntryBlueprint = $fieldsBlueprint['items']['fields'] ?? null;

foreach ($page->items()->toStructure() as $entry) {
  doSomethingWith($entry, $structureEntryBlueprint);
}

As opposed to simply:

foreach ($page->items()->toStructure() as $entry) {
  doSomethingWith($entry); // get the blueprint from within the function
}

Passing along the blueprints as an argument is unnecessary pollution. But the big problem is that the Site, Page and File have a blueprint() method, while StructureObject doesn't. This means that if you want to create a function that processes models recursively, you have to handle occurrences of StructureObject separately and pass blueprints in the arguments, while the other classes don't need that.

This is a snippet from a class I'm making:

/**
 * Recursively walks over a Model.
 * @param Kirby\Cms\Model $model
 * @param array|null $blueprint used for models without a blueprint() method
 * like StructureObject
 * @return array|null
 */
public function walk(Model $model, array $blueprint = null)
{
    if (!$blueprint) {
        $blueprint = $model->blueprint()->fields();
    }

    $data = null;
    $content = $model->content($this->settings['language']);

    foreach ($blueprint as $key => $fieldBlueprint) {
        $field = $content->$key();
        $fieldData = $this->walkField($fieldBlueprint, $field);

        if ($fieldData !== null) {
            $data[$key] = $fieldData;
        }
    }

    return $data;
}

In the walkField method, I call walk when the field is of type structure, so recursion occurs.

While it works fine, the whole $blueprints part is kind of ugly. My whole idea with this issue is to unify the API so you can handle recursion easier.