8

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

Craft\EntryModel 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 5

10

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'] %}
3
  • Is this still true? I have an environment where a field is assigned to an entry type, and another environment where it isn't—so the entry type check won't solve this. (Nevermind that my situation is questionable.)
    – Matt Stein
    Feb 13, 2015 at 1:47
  • 2
    @MattStein Yeah. I recently spent some time trying to look into it again but Twig basically just assumes something actually is defined if it can’t be determined. In your case you would just need to add an additional check in the conditional for the environment. (You can give each environment config a custom 'env' key set and check with craft.config.env.) Feb 18, 2015 at 14:28
  • @BrandonKelly So to Twig, a falling tree makes a sound no matter what. Thanks for the update and environment-check approach!
    – Matt Stein
    Feb 18, 2015 at 15:38
16

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:

4
  • 3
    This should go into the official documentation! ... I just dumped several hours into researching this odd behaviour :(
    – m9dfukc
    Jan 18, 2016 at 19:34
  • Sweet, that resolves it. Thanks for sharing! Mar 21, 2016 at 10:02
  • 2019 and this is still an issue - and this appears to still be a work-around. Thanks!
    – Mike
    Oct 31, 2019 at 23:16
  • This had me flummoxed, thank you!
    – Judd Lyon
    Jan 6, 2021 at 2:38
1

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.

1

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 %}
0

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.

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.