7

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?

9

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'] %}
  • 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 '15 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.) – Brandon Kelly Feb 18 '15 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 '15 at 15:38
12

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:

  • 2
    This should go into the official documentation! ... I just dumped several hours into researching this odd behaviour :( – m9dfukc Jan 18 '16 at 19:34
  • Sweet, that resolves it. Thanks for sharing! – medoingthings Mar 21 '16 at 10:02
  • 2019 and this is still an issue - and this appears to still be a work-around. Thanks! – Mike Oct 31 at 23:16
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.

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, privacy policy and cookie policy

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