TransWikia.com

Twig "is defined" always returning true

Craft CMS Asked by Marion Newlevant on August 30, 2020

I am trying to code defensively and check whether a field exists before accessing it:

{% if e.someField is defined %} {{e.someField}} {% endif %}

And I get a CException

CraftEntryModel and its behaviors do not have a method or closure named “someField”.

Because is defined is always returning true.

Any clue what’s wrong?

5 Answers

This is a known issue with Twig’s ‘defined’ test - it’s not very reliable when it comes to object properties. We plan on looking further into it at some point and coming up with a formal test case that reproduces the issue, plus a bug fix. Just haven’t gotten around to it yet.

In the meantime, rather than testing if a field is defined, check the entry type:

{% if e.type == 'news' %}

If you want to check multiple entry/block types at once, you can use the in operator:

{% if e.type.handle in ['news', 'blog'] %}

Correct answer by Brandon Kelly on August 30, 2020

I came across this while trying to check if object has method. I found out that is defined always returns true and some other options would return always null (even if the method existed) so I ended up writing a twig extension (which was faster than trying to solve the problem):

/**
 * @param object $entity
 * @param string $method
 *
 * @return bool
 */
public function twig_method_exists($entity, string $method): bool
{
    if (!is_object($entity)) {
        throw new InvalidArgumentException('Given entity is not of type object, got: '.get_class($entity));
    }

    if (empty($method)) {
        throw new InvalidArgumentException('Method name empty');
    }

    return method_exists($entity, $method);
}

And i use it like this in the twig

{% if method_exists(entity, 'getCreatedBy') %}
    ({{ entity.createdBy.name }})
{% endif %}

Answered by Tuomas Valtonen on August 30, 2020

I had a similar issue when using an if statement to define a block element. The correct way doing this will be following:

{% block metaOgImage %}
  {% if og.image is defined %}
   <meta property="og:image" content="{{ og.image }}">
  {% endif %}
{% endblock %}

Do not use the if statement outside the block statement.

Answered by Stephan on August 30, 2020

I believe I have a workaround and can (roughly) explain what's going on here.

First, the workaround:

Use array syntax to test the existence of your custom fields instead of object syntax.

{# This works #}
{% if entry['someField'] is defined %} ... {% endif %}

Using array syntax for your conditional will result as true if your field does exist and false if it does not. Using object syntax results in false positives where the conditional always returns true even if a field doesn't exist:

{# FALSE POSITIVES: These return true if a field doesn't exist #}

{% if entry.someField is defined %} ... {% endif %}
{% if attribute(entry, 'someField') is defined %} ... {% endif %}

I believe the false positive occurs using object syntax when the object has or is extending another object that uses a __call() method. Twig will test to see if it can find an attribute or a method. In the case it can't find an attribute on an object, it will look for a method. And if it can't find a method on the object, it will check for the existence of a __call() method and if the _call() method exists it returns true, creating the undesired result of also returning true for any variable that doesn't exist on an object that uses a __call() method.

I haven't dug into the code, but it seems that using the array syntax doesn't trigger a search for a potential method associated with the __call() behavior.


A couple related articles can be found on Straight Up Craft:

Answered by Ben Parizek on August 30, 2020

I was able to resolve this issue by creating a custom plugin that uses a service and template variables. The service will accept the content object and field name. You can reference how to write a conditional to check if the field is set (http://buildwithcraft.com/classreference/models/BaseElementModel#getFieldValue-detail). The service is then used in the template variables and will return true or false based on if the field is set.

Craft Template Twig Example

{{ (craft.pluginName.isFieldDefined(e, 'someField') ? e.someField : false) }}

Sorry, I can't post a full code example.

Answered by Mike Zens on August 30, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP