Support

Account

Home Forums Bug Reports acf_get_value notice incorrect

Solved

acf_get_value notice incorrect

  • Hi,

    After updating ACF I’m getting the acf_get_value notices and I think it’s incorrect. https://www.advancedcustomfields.com/resources/acf-field-functions/

    Here’s what I found when debugging so far:
    – register a new options field using acf_add_local_field_group()
    – after that get the field using it’s name get_field("name", 'option');

    This results in a notice but does not when using the field key instead of the name.
    When adding the _option_ -> field_ mapping in the DB using get value with the name also works just fine.

    My current workaround solution:
    Fetch the ID from the local store to prevent the get_value code to go down the path triggering the notice.

    add_filter('acf/validate_field', function (Array $field): Array {
       if (!$field['key']) {
         $name = $field['name']; 
         $fields = acf_get_local_store('fields');
            
         if ($fields && array_key_exists($name, $fields->aliases)) {
           $field['key'] = $fields->aliases[$name];
         }
        }
    
        return $field;
    });

    Expected behavior, notice should only trigger if the field is not registered.
    Get value should fetch the id from the local store or the usual flow using the DB.

  • I did a bit more testing on my workaround and I noticed something unexpected with the return type.
    When fetching by name a toggle (true/false) field returns a string "1" but doing the same with a key returns a bool true.

    I am now using the following workaround:

    
    add_filter('acf/pre_load_value', 'getFieldByName', 10, 3);
    function getFieldByName(mixed $value, string|int $post_id, array $field): mixed
    {
        if (!$field['key']) {
            $name = $field['name'];
            $fields = acf_get_local_store('fields');
                
            if ($fields && array_key_exists($name, $fields->aliases)) {
                return get_field($fields->aliases[$name], $post_id);
            }
        }
    
        return $value;
    }
    
  • What exactly is this doing $fields->aliases[$name]

    Are you attempting to use field names that are not actually the field names?

    ACF no longer allows getting fields using field names that are not actually defined in the fields or for fields that are defined somewhere else.

    For example, trying to use get_field('_thumbnail_id') for the post thumbnail instead of get_post_meta($post_id, '_thumbnail_id', true) will result in an error/warning because this is not a field defined ACF (usually)

    https://www.advancedcustomfields.com/blog/acf-5-11-release-rest-api/

    https://www.advancedcustomfields.com/blog/acf-5-11-1-release/

    So of you are doing something that uses a field name other than the defined field name then you’re going to get this error. ACF is not finding a field defined with that name. The reason that using the field key does not create the error is that the field key never changes and ACF finds the field associated with the field key.

  • Hi John,

    
    $fields = acf_get_local_store('fields');
    $fields->aliases
    

    This is the internal array in the ACF store that holds a mapping with field names to field ids.

    Here’s a very minimalistic representation of what I’m doing:

    
            acf_add_local_field_group([
                "key" => "group_Feature toggles",
                "title" => "Feature Toggles",
                "fields" => [
                    [
                        "type" => "true_false",
                        "name" => "anchor_tags",
                        "label" => "Anchor Tags",
                        "key" => "field_feature_toggles_anchor_tags",
                    ],
                ]
            ]);
            get_field('anchor_tags', 'option');
    

    The aliases array contains:
    "anchor_tags" => "field_feature_toggles_anchor_tags"

    So my pre load value work around just fetches the field key from the internal store and uses that to fetch the value instead of the name solving the issue.

  • So then it comes down to when the field group is defined. I don’t see any reason that you would get the warning/error on the acf/pre_load_value hook other than the field group/fields not being defined before the hook occurs.

  • Hi John,

    I’m not getting an error on the hook. The hook is a workaround.
    The error occurs on normal use.

    The field group is defined in a class that is directly loaded from the functions.php. Usage is as in my example, define the fields then fetch the value after.

    As mentioned in my initial post this only occurs for new options. The error is resolved after actually saving the option once on the settings page. This is because the database also holds a key -> name mapping resolving the issue.

  • If you use acf_add_local_field_group() before acf/init and then immediately try to get a field in that group then you are likely trying to get the field before acf/init unless you are adding the field group on the acf/init hook. The documentation on this says you can do one acf/init OR in functions.php, but the documentation could be wrong.

    Here is the code before the error is shown

    
    if ( did_action( 'init' ) ) {
      return;
    }
    /// outputs error here
    

    if you call get_field() before init has fired you will get the error.

  • I also looked at the documentation and read the same “or”.
    I tested with the init hook and had the same result I think. I will test again and set up a clean WP install with minimal code to reproduce the issue if it persists. If it does I will share a repo.

  • Hi John,

    I’ve tested again with a clean install to make sure it’s not in my codebase.
    I took the latest WP 5.9 + latest ACF 5.11.4 and added the following to the function.php to trigger the error:

    
        acf_add_local_field_group([
            "key" => "group_Feature toggles",
            "title" => "Feature Toggles",
            "fields" => [
                [
                    "type" => "true_false",
                    "name" => "anchor_tags",
                    "label" => "Anchor Tags",
                    "key" => "field_feature_toggles_anchor_tags",
                ],
                ]
            ]);
            
        get_field('anchor_tags', 'option');
    

    Yes wrapping it in acf/init suppresses the notice but as the documentation states it should not be required. acf_maybe_get_field triggers init before it gets the value. So even without wrapping it will always be initialized looking at the code.

    I’ve been digging in the ACF codebase and I think the root cause lies within the acf_maybe_get_field code:

    
    /*
    *  acf_get_object_field
    *
    *  This function will return a field for the given selector.
    *  It will also review the field_reference to ensure the correct field is returned which makes it useful for the template API
    *
    *  @type    function
    *  @date    4/08/2015
    *  @since   5.2.3
    *
    *  @param   $selector (mixed) identifyer of field. Can be an ID, key, name or post object
    *  @param   $post_id (mixed) the post_id of which the value is saved against
    *  @param   $strict (boolean) if true, return a field only when a field key is found.
    *  @return  $field (array)
    */
    function acf_maybe_get_field( $selector, $post_id = false, $strict = true ) {
    
    	// init
    	acf_init();
    
    	// Check if field key was given.
    	if ( acf_is_field_key( $selector ) ) {
    		return acf_get_field( $selector );
    	}
    
    	// Lookup field via reference.
    	$post_id = acf_get_valid_post_id( $post_id );
    	$field   = acf_get_meta_field( $selector, $post_id );
    	if ( $field ) {
    		return $field;
    	}
    
    	// Lookup field loosely via name.
    	if ( ! $strict ) {
    		return acf_get_field( $selector );
    	}
    
    	// Return no result.
    	return false;
    }
    

    When get_field is called the second thing it does is call acf_maybe_get_field.
    So what happens:
    1) init
    2) check if key (it’s not)
    3) try to get the field using the hidden metadata using acf_get_meta_field which will fail because it has not yet been saved to the database.
    4) $strict is true so it will not trigger acf_get_field
    5) now we return false.

    The acf_get_meta_field only looks at the DB so this is where I think it initially goes wrong and because this method runs in strict mode it never calls acf_get_field which would resolve the field just fine.

    Why does acf_get_meta_field only look at the DB if the local store has a mapping between field keys and names?
    Why was this strict mode introduced? Did ACF stop supporting getting a value by name? That would explain a lot.

    How I think this issue should be resolved:
    acf_get_reference which is called by acf_get_meta_field should check the local store to map a name to a key and fall back to the DB when it can’t find one although I think that situation would trigger this new notice. So I wonder if fetching this info from the DB would be needed at all.

  • Yes, wrapping the get_field() call inside an acf/init action in function.php is required. You can define the field group without it but if you try to get a value you are getting a value directly inside functions.php that happens before init.

  • Going through all the code I realized I could do a better performing work around which is the following:

    
    add_filter('acf/pre_load_reference', function ($reference, $name, $postId) {
        $fields = acf_get_local_store('fields');
    
        return $fields && array_key_exists($name, $fields->aliases) ? $fields->aliases[$name] : $reference;
    }, 10, 3);
    
  • Yes, wrapping the get_field() call inside an acf/init action in function.php is required. You can define the field group without it but if you try to get a value you are getting a value directly inside functions.php that happens before init.

    No, it is not. Not according to documentation and also not looking at the code.

    Please follow the code.
    1) get_field
    2) acf_maybe_get_field
    3) acf_init();
    ….
    Getting the actual value happens after these steps. Always.

  • After testing my last fix my total queries on my test page went down from ~190 to 140. I think this filter might be an overall improvement in reducing queries to fetch the field keys from the DB not just in my case.

  • It is no surprise to me that using field keys instead of field names reduces queries. If a query needs to be done to get a field and the field name is used ACF must first to a query to get the field key reference as well, 2 queries for each field.

    If you must get field values before WP caches the meta values for a post then calling get_post_meta($post_id) will cause WP to cache all meta values for a post and reduce queries because ACF will not cause a query for each field.

  • It seems like poor design, needing an additional roundtrip per field you get by name.
    Why not resolve it from the internal storage like I’m doing right now? Or not support fetching by name if it’s inefficient.
    With the new notice, you should always have the key mapping in the internal store anyway.

    If you must get field values before WP caches the meta values for a post then calling get_post_meta($post_id) will cause WP to cache all meta values for a post and reduce queries because ACF will not cause a query for each field.

    That is interesting to know but I guess won’t work with options that are heavily used in my case.

  • It usually does not happen. Most of the time, not all of the time but most of the time, a query has been performed and a post has been retrieved. This post retrieval is supposed to cause all meta data for the post to be retrieved in a single query, though you can force it not to happen. In any case, with this there are not extra queries for getting meta data because it is in the WP cache.

    Extra queries will also be run if you use get_post_meta($post_id, ‘meta_key’), only in this case it is only one extra query instead of 2. Forcing WP to get all meta values with get_post_pets($post_id) [no field name] when you are getting meta values without getting a post is a long established performance optimization.

  • I’m going to mark this thread as resolved with my own workaround but I still think this is a bug in the library and should be addressed. Using the init hook does not solve the issue it only suppresses the notice and actually returns the wrong return type!

    To conclude, in my opinion:
    ACF should either drop support for get_value by name or make acf_get_reference also look in the local store to resolve name -> key mapping instead of only relying on the DB or internal cache.

  • Ok, this really confuses me, I was in contact using the form you linked.
    The reply was that this kind of “custom usage” is beyond the scope of the support desk. I was referred to the forum and was told dev support was helping me. So I was under the impression the forums should be used for this kind of support.

    Guess I will retry.

  • I am sorta support, but I’m really just a power user and I answer questions on this forum. I can’t really do anything with bugs. On the other hand I don’t see this as a bug. You yourself said that you are attempting to get a field value before init, this in my opinion is the reason for the error/warning you are seeing.

  • You yourself said that you are attempting to get a field value before init, this in my opinion is the reason for the error/warning you are seeing.

    I don’t think I said that anywhere in this thread. I said the init is always performed and as you mentioned the documentation states you do not have to wrap the get_value in an init hook. Please open up the acf_maybe_get_field function you will see the init call.

    The notice is just suppressed when you wrap all code in acf/init hook but the return value is still incorrect, it’s different when using the key to get the value. It would return 1 instead of true in my case and this might cause unexpected side effects. This is a bug in my opinion. And if it works with a key without wrapping with an init hook it should also work with a name in my opinion, which technically is a simple fix and as a side effect improves performance for some users. Not sure why you would not want that.

  • In this comment you stated:

    Yes wrapping it in acf/init suppresses the notice but as the documentation states it should not be required. acf_maybe_get_field triggers init before it gets the value. So even without wrapping it will always be initialized looking at the code.

    In addition, acf_maybe_get_field() triggers acf/init and it does this prematurely. The code in ACF is looking to see if the main WP “init” hook has been fired, as I posted in this comment. Initializing ACF prematurely does not cause this hook to fire prematurely.

    Premature initialization of ACF can cause all kinds of undesirable side effects.

  • This:

    The notice is just suppressed when you wrap all code in acf/init hook but the return value is still incorrect, it’s different when using the key to get the value. It would return 1 instead of true

    is an indication that ACF is not finding the field key reference in order to get the field definition. It can also be caused by other filters, most notably pre_get_post filters.

    In the former case ACF stores a value of 1 in the DB for a true/false field. If it cannot locate the field definition then it returns whatever value is stored in the DB without formatting it correctly.

    The latter case can be very difficult to track down then exact cause.

  • Update after using my workaround for a while now.
    Field names are not unique and the internal ACF store just overwrites name => key mapping when a duplicate is added. When using my workaround this makes get value potentially return the wrong field key.

    For example, using the following setup would always return the key for field group 2:
    field group 1
    – field “link”
    field group 2
    – field “link”

    I’ve expanded my workaround to make a difference between options and post fields assuming options are actually unique making options always do a DB roundtrip again.

Viewing 24 posts - 1 through 24 (of 24 total)

You must be logged in to reply to this topic.