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: