See https://wiki.php.net/rfc/named_params and scroll down to call_user_func_array().

call_user_func_array() is used by Rules core to invoke conditions/actions, which each define their own set of context variables that need to passed in as parameters.

Core dealt with this in #3174022: call_user_func_array() and named arguments in PHP 8. I will roll a patch for this later today.

Issue fork rules-3210303

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

TR created an issue. See original summary.

tr’s picture

Issue summary: View changes
tr’s picture

Status: Active » Needs review
StatusFileSize
new9.65 KB

  • TR committed cc5ba05 on 8.x-3.x
    Issue #3210303 by TR: PHP 8 introduced breaking change to...
tr’s picture

Status: Needs review » Fixed

Committed #3 with the coding standards problem fixed.

jonathan1055’s picture

Hi TR,
I've looked at the two links you gave in the summary, and then looked at the commits you have made. I must be missing something as I can't see the connection between renaming the variables $account to $user, $entity_type to $type and $pos to $position and the original call_user_func_array() change. Are those variable names used later, or are they now protected names or something?

tr’s picture

PHP 8 introduced a new "named parameters" syntax.

If you define a function myfunction($one, $two, $three), in addition to invoking it like $result = myfunction(1, 2, 3);, PHP 8 now allows you invoke it like $result = myfunction(one: 1, two: 2, three: 3);. Note that the name of the formal argument is used as a prefix to the value. If this syntax is used, then it is no longer necessary to pass the values in the same order they were defined. Thus, another valid and equivalent way to call this is $result = myfunction(two: 2, three: 3, one: 1);. PHP will match the names of the arguments and deliver them to the function in the correct order - when using this syntax the order of the values being passed no longer matters.

Another way to invoke this function is to use $array = [1, 2, 3]; $result = call_user_func_array('myfunction', $array);. In Rules we do this to call the doExecute() method of conditions and actions because the arguments to the doExecute() function depend on the context variables defined in the annotation for those conditions and actions. We put all the context variables into an array indexed by the name of the context variable then pass that array as the second argument to call_user_func_array().

The problem arises when the name of the context variable in the annotation doesn't match the name of the variable used in the definition of doExecute(). In PHP 8, call_user_func_array() will now internally use the named parameter syntax to call the function, so if we pass an array like $array = ['uno' => 1, 'dos' => 2, 'tres' => 3]; then $result = call_user_func_array('myfunction', $array); will now fail because instead of passing the array arguments in order it will try to use the keys of the array to match the function's arguments. Because there is no formal argument $uno (we called it $one) PHP will complain about an unknown named argument $uno.

What this means in concrete terms, using the UserIsBlocked condition as an example, is that if we define a context variable 'user' in the annotation, then doExecute() now MUST have an argument with the variable name $user - nothing else is allowed. We were using $account for the name of the variable in doExecute(), and that was and is perfectly valid in PHP 8 and previous versions of PHP UNLESS you use call_user_func_array(), in which case the context variable name needs to match exactly the variable name used within doExecute().

In the case of 'pos' I changed the context variable name instead of the variable name in doExecute() because the full name is more descriptive and usable, and abbreviating 'position' doesn't save anything.

jonathan1055’s picture

Thank you so much for taking the trouble to explain all that. It is really helpful, not just for me, but for others who may be discovering the same problems.

liam morland’s picture

The could also be fixed by passing the second parameter to call_user_func_array() through array_values() so that it doesn't have keys that would be treated as named parameters.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

glynster’s picture

@TR we just upgraded some of our sites to PHP8 and came across this error even with the latest rules:

"drupal/rules": "3.x-dev@dev",

Patch
"Rules registers no listeners on rare occasions": "https://www.drupal.org/files/issues/2022-01-31/2816033-102-no-listeners....",

Error: Unknown named parameter $user in call_user_func_array() (line 152 of /cms/releases/20220219064741/web/modules/contrib/rules/src/Core/RulesActionBase.php)

Not sure if I am doing the right thing by adding a comment here or if I need to create a new case.

tr’s picture

@glynster: Are you using actions or conditions provided by some module other than Rules? Those may not be PHP 8 compatible.

glynster’s picture

Yes we are using your other module and rules flag:

"drupal/tr_rulez": "1.x-dev@dev",
"drupal/rules_flag": "1.0.x-dev",
"drupal/role_expire": "1.x-dev",

That said I have narrowed it down to core Rules (I think). Here is the rule that is causing the issue:

uuid: 79e7c8a0-dd77-4345-862b-1e230a60742d
langcode: en
status: true
dependencies: {  }
id: first_time_login
label: 'First Time Login'
events:
  -
    event_name: rules_user_login
description: 'First time the user has logged in.'
tags: {  }
config_version: '3'
expression:
  id: rules_rule
  uuid: cbdf8bda-7102-459d-9186-de2b4cf01932
  weight: 0
  conditions:
    id: rules_and
    uuid: 7614fb56-b1bb-4770-a40d-b656ff6df517
    weight: 0
    conditions:
      -
        id: rules_condition
        uuid: 32f1bb93-0b81-4239-99bb-e7000f2b3ac5
        weight: 0
        condition_id: rules_user_has_role
        negate: false
        context_values:
          roles:
            - subscriber
            - access
          operation: OR
        context_mapping:
          user: account
        context_processors:
          roles:
            rules_tokens: {  }
          operation:
            rules_tokens: {  }
        provides_mapping: {  }
      -
        id: rules_condition
        uuid: bbc367e2-1afa-494f-b9e2-afad74038813
        weight: 0
        condition_id: rules_data_is_empty
        negate: false
        context_values: {  }
        context_mapping:
          data: account.access.value
        context_processors: {  }
        provides_mapping: {  }
  actions:
    id: rules_action_set
    uuid: 7f456550-f51b-4747-bb5c-754a42fabc36
    weight: 0
    actions:
      -
        id: rules_action
        uuid: 5eae739c-1ac3-4418-b56d-20e8d3862179
        weight: 0
        action_id: rules_user_role_add
        context_values:
          roles:
            - first_timer
        context_mapping:
          user: account
        context_processors:
          roles:
            rules_tokens: {  }
        provides_mapping: {  }
      -
        id: rules_action
        uuid: d4242de4-726d-45e5-b0f9-8008a02085b7
        weight: 0
        action_id: role_expire_set_expire_time
        context_values:
          roles:
            - first_timer
          date: '1 minute'
        context_mapping:
          user: account
        context_processors:
          roles:
            rules_tokens: {  }
          date:
            rules_tokens: {  }
        provides_mapping: {  }

I wonder if it is related to role_expire_set_expire_time

glynster’s picture

When I remove this action from the above rule it works:

Set expire time for user roles
Parameters: user: account, roles: [first_timer], date: 1 minute
tr’s picture

That problem is with the role_expire module - both of the actions in that module need to be modified to be compatible with PHP 8.1.

The needed change for those actions is what I described in #7:

if we define a context variable 'user' in the annotation, then doExecute() now MUST have an argument with the variable name $user - nothing else is allowed

That change is as easy as renaming the variable from $account to $user in doExecute().

That module should also really have some test cases so they can verify that it really works with all supported versions of PHP and core Drupal.

glynster’s picture

@TR yup as always you were right. I am going to create a patch and add it to the role_expire issues. Thanks for pointing me in the right direction!

glynster’s picture

@TR here is the case and patch: https://www.drupal.org/project/role_expire/issues/3265491#comment-14418022. Resolved the problem for us. Works on PHP7.4 and PHP8 <

drcni made their first commit to this issue’s fork.

yan.jaunky’s picture

Assigned: tr » yan.jaunky
Category: Task » Bug report
StatusFileSize
new521 bytes

Hello,
I did a small update in the file RulesActionBase.php concerning call_user_func_array. It throws an exception Error : Unknown named parameter.

yan.jaunky’s picture

StatusFileSize
new548 bytes

Another update was done in RulesConditionBase.php

tr’s picture

Assigned: yan.jaunky » tr
Category: Bug report » Task

This issue was closed and fixed almost two years ago. There is nothing more that needs to be done here.