1

I'm creating a Custom Field Type whose possible values depend on the Globals. Therefore, I'm querying the Globals from the Custom Field's init():

class CustomField extends Checkboxes
{
    public function init()
    {
        // This line causes 502 Bad Gateway.
        $image = Craft::$app->globals->getSetByHandle('general')->image->one();
        // The rest is removed for clarity.
    }
}

class CustomFieldTypes extends Module {
  public function init()
  {
      // Register our Field
      Event::on(
          Fields::class,
          Fields::EVENT_REGISTER_FIELD_TYPES,
          function (RegisterComponentTypesEvent $event) {
              Craft::debug(
                  'Fields::EVENT_REGISTER_FIELD_TYPES',
                  __METHOD__
              );
              $event->types[] = CustomField::class;
          }
      );
  }

}

However, the above results in 502 Bad Gateway. When I place the query into the CustomFieldTypes module, it works as expected.

When looking at the logs, the following query

SELECT `fields`.`id`, `fields`.`dateCreated`, `fields`.`dateUpdated`, `fields`.`groupId`, `fields`.`name`, `fields`.`handle`, `fields`.`context`, `fields`.`instructions`, `fields`.`translationMethod`, `fields`.`translationKeyFormat`, `fields`.`type`, `fields`.`settings`, `fields`.`uid`, `fields`.`searchable`, `fields`.`columnSuffix`
FROM `fields` `fields`
ORDER BY `fields`.`name`, `fields`.`handle`

gets ran almost 2000 times, among others.

Can you please help me find what's causing the error and what workaround should I use?

Extra info

I'm using Craft Pro 3.7.67 with plugins:

Control Panel CSS   2.4.0
Excerptify  1.0.1
Freeform    3.13.25
Mailchimp Subscribe 3.1.1.1
Navigation  1.4.31
Neo 2.13.16
Opening Hours   1.0.0
Redactor    2.10.11
Redactor Custom Styles  3.0.4
Redirect Manager    1.1.1
SS Entry Importer   1.0.3
Super Table
4
  • Are you seeing any error messages in the logs?
    – MoritzLost
    Mar 6, 2023 at 16:38
  • Good point, here's the full log: drive.google.com/file/d/18okzRLZ6XfkK90Rv-pmoTARDsx3DL_z_/view There's one query repeated almost 2000 times, I updated my answer. I'll be very grateful if you can think of why this is the case. Mar 6, 2023 at 16:57
  • Probably not a good idea to execute an element query in a custom field type's init() method. Can you describe the use case for when/where/why the $image asset is needed in your field type? Mar 7, 2023 at 11:19
  • Thanks a lot for chiming in. The image example is made up, but basically, I'm trying to give our Content Creators an option from the Admin Panel through the use of Globals to affect options of a Checkbox field. For example, having a Color checkbox field whose options can be altered from the Admin Panel. What would you say is the idiomatic way to achieve this in CraftCMS? (I'm a new maintainer of the project and I don't have a lot of experience with Craft) Mar 7, 2023 at 18:11

2 Answers 2

2

The answer below is mostly taken from Brandon Kelly's answer on Github which I suggest to read for more detailed information: https://github.com/craftcms/cms/issues/12847

The reason for the endless recursion is that fetching globals creates an element query. Element queries need to fetch all custom fields to give them a chance to modify the query. Therefore, fetching Globals in the init() of a Custom Field creates an endless recursion.

The solution is to fetch the Globals elsewhere, for example in inputHtml() or settingsHtml(). However, the Dropdown and alike expect their options to be set from the init() and not change dynamically. To avoid problems, one can inherit from Field directly, for example:

use craft\base\ElementInterface;
use craft\base\Field;
use craft\helpers\Cp;

class SphColorField extends Field
{
    /**
     * @var array
     */
    private $options;

    protected function inputHtml($value, ElementInterface $element = null): string
    {
        return Cp::selectFieldHtml([
            'id' => $this->getInputId(),
            'describedBy' => $this->describedBy,
            'name' => $this->handle,
            'value' => $value,
            'options' => $this->options(),
        ]);
    }

    private function options(): array
    {
        if (!isset($this->options)) {
            $this->options = [];
            $global = Craft::$app->globals->getSetByHandle('globalTest');
            if ($global) {
                foreach ($global->entries as $entry) {
                    $this->options[] = [
                        'label' => $entry['title'],
                        'value' => $entry['id']
                    ];
                }
            }
        }

        return $this->options;
    }
}
1

When running code inside the init() function for a field type, you're running it every time the field is instantiated. Typically you would put Event functions inside of the init, which tells the system when to run that code. Here's the docs:

https://craftcms.com/docs/4.x/extend/field-types.html#registering-custom-field-types

Event::on(
            Fields::class,
            Fields::EVENT_REGISTER_FIELD_TYPES,
            function(RegisterComponentTypesEvent $event) {
                $event->types[] = MyField::class;
            }
        );

I think for your use case you might want this EVENT_REGISTER_FIELD_TYPES but the other event codes can be found here:

https://craftcms.com/docs/4.x/extend/events.html#event-code-generator

1
  • I linked to Craft 4.x documents but for this, its the same for 3.x, if you need to swap docs there is a drop down on the top left for Craft CMS version number.
    – cherrykoda
    Mar 8, 2023 at 20:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.