Log1x / acf-composer

Compose ACF Fields, Blocks, Widgets, and Option Pages with ACF Builder on Sage 10.
https://github.com/Log1x/acf-composer
MIT License
413 stars 56 forks source link

Feature Request for ACF with() method to allow for returning of object in place of array #121

Closed kupoback closed 1 year ago

kupoback commented 2 years ago

In Sage 9, in the Controller Files, the protected $acf array returns each instance of a field into an object. I really liked working with that, and was wondering if there was a way, either as an option, or just natively, to do the same.

I am working with your current package, but with Sage 10, made an AcfNestedFields.php Class, and ported over most of the functionality. I was wondering if you would maybe consider using this as inspiration to implement into this great package.

Acf Block Class

class TextBlock extends Block
{
    //... previous block variables and methods

    /**
     * Field Names used for this block
     * @var array|string[]
     */
    public array $fieldNames = [
        'title',
        'content',
        'items',
        'cta',
        'image',
        'cta_title',
    ];

    /**
     * Data to be passed to the block before rendering.
     *
     * @return array
     */
    public function with()
    :array
    {
        return (new AcfNestedFields($this->fieldNames))
            ->getData();
    }

    //.. other block methods
}

AcfNestedFields.php

<?php

namespace App\SageThemeModule;

use Illuminate\Support\Str;
use function collect;

class AcfNestedFields
{
    /**
     * @param array $data The field names to grab get_field data for
     */
    public function __construct(
        protected array $data = [],
        private bool $returnArrayFormat = false
    ) {
        $this->setReturnFilter();
        $this->setData($this->data);
    }

    /**
     * Set Return Filter
     *
     * Return filter sober/controller/acf-array
     */
    private function setReturnFilter()
    :void
    {
        // This filter might have to be recreated, but I wasn't able to
        // fully find the add_filter for this in Sage 9's vendor files
        $this->returnArrayFormat =
            (has_filter('sage/classes/acf/array')
                ? apply_filters('sage/classes/acf/array', $this->returnArrayFormat)
                : false);
    }

    /**
     * Iterates over array and adds a new snake cased key, with orignial value, for each kebab cased key
     *
     * Return void
     */
    private function recursiveSnakeCase(&$data)
    :void
    {
        if (!is_array($data)) {
            return;
        }

        collect($data)
            ->each(fn($val, $key) => is_array($val)
                ? $this->recursiveSnakeCase($val)
                : $data[Str::kebab($key)] = $val);
    }

    /**
     * Convert the data for the fields to an object if returnArrayFormat is false
     *
     * @return void
     */
    public function setDataReturnFormat()
    :void
    {
        if ($this->returnArrayFormat) {
            return;
        }

        if ($this->data) {
            collect($this->data)
                ->each(fn($item, $key) => $this->data[$key] = json_decode(json_encode($item)));
        }
    }

    /**
     * Set Data
     *
     * Set data from passed in field keys
     */
    public function setData($acf)
    :void
    {
        if (is_string($acf)) {
            $this->data = [$acf => get_field($acf)];
        }

        if (is_array($acf)) {
            collect($acf)
                ->each(fn($item) => $this->data[$item] = get_field($item));
        }

        $this->recursiveSnakeCase($this->data);

        // Convert the data to an object
        $this->setDataReturnFormat();
    }

    /**
     * Get Data
     *
     * Return the data
     *
     * @return array
     */
    public function getData()
    :array
    {
        return $this->data;
    }
}
Log1x commented 2 years ago

Have you taken a look at https://github.com/roots/acorn/blob/main/src/Roots/Acorn/View/Composers/Concerns/AcfFields.php ?

kupoback commented 2 years ago

Have you taken a look at https://github.com/roots/acorn/blob/main/src/Roots/Acorn/View/Composers/Concerns/AcfFields.php ?

Yeah, I see it now, it's a lot cleaner, something I could adapt to instead of what I migrated above. I don't like the idea of get_fields() in an acf block, when I know what those field names are.

kupoback commented 2 years ago

So I took a look at the above Class again, and played around with a bit more, and ended up with this. However, I will admit I am not super familiar with Laravel Class and methods, so I would love to know if this is the right route? I had to actually call to the toJson() method, and decode it to get the results i was looking for.

public function getFields()
:array
{
    return collect($this->data)
        ->mapWithKeys(fn($value) => [$value => get_field($value)])
        ->mapWithKeys(function ($value, $key) {
            $value = is_array($value)
                ? json_decode((new Fluent($value))->toJson())
                : $value;
            $method = Str::camel($key);
            return [$key => method_exists($this, $method) ? $this->{$method}($value) : $value];
        })
        ->all();
}
Log1x commented 1 year ago

Sorry for the late reply. That looks good. You could maybe do (object) $value instead of the json_decode() but I don't think that will handle objects recursively.

Going to close this for now as it's not entirely in scope for ACF Composer at the moment – although what you have does look useful. 😄