Skip to content

Using null as an array offset is not deprecated #20194

Closed

Description

@thomas-0816

Description

The following code:

<?php

$a = [null => 1];

echo $a[null];

Resulted in this output:

Deprecated: Using null as an array offset is deprecated, use an empty string instead in /var/www/test.php on line 5
1

But I expected this output instead:

Deprecated: Using null as an array offset is deprecated, use an empty string instead in /var/www/test.php on line 3
Deprecated: Using null as an array offset is deprecated, use an empty string instead in /var/www/test.php on line 5
1

PHP Version

PHP 8.5.0RC2 (cli) (built: Oct  9 2025 23:32:25) (NTS)
Copyright (c) The PHP Group
Built by https://github.com/docker-library/php
Zend Engine v4.5.0RC2, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.0RC2, Copyright (c), by Zend Technologies

Operating System

Docker php:8.5.0RC2-cli-bookworm

Activity

  1. ndossche commented on Oct 18, 2025

    @ndossche
    Member

    Probably this, but I again don't like how deprecations in the core propagate through everything and make things more complicated than they should be.
    Not sure yet if the changes in zend_try_ct_eval_array are necessary, i.e. if we're allowed to emit a deprecation even for non-executed code?

    diff --git a/Zend/Optimizer/sccp.c b/Zend/Optimizer/sccp.c
    index bbf52d8cf69..38a28dcd488 100644
    --- a/Zend/Optimizer/sccp.c
    +++ b/Zend/Optimizer/sccp.c
    @@ -1106,6 +1106,11 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
     
     			if (op2) {
     				SKIP_IF_TOP(op2);
    +				if (Z_TYPE_P(op2) == IS_NULL) {
    +					/* Emits deprecation at run-time. */
    +					SET_RESULT_BOT(result);
    +					return;
    +				}
     			}
     
     			/* We want to avoid keeping around intermediate arrays for each SSA variable in the
    diff --git a/Zend/zend_API.c b/Zend/zend_API.c
    index 1b97974686e..4065e7a8618 100644
    --- a/Zend/zend_API.c
    +++ b/Zend/zend_API.c
    @@ -2256,6 +2256,7 @@ ZEND_API zend_result array_set_zval_key(HashTable *ht, zval *key, zval *value) /
     			result = zend_symtable_update(ht, Z_STR_P(key), value);
     			break;
     		case IS_NULL:
    +			zend_error(E_DEPRECATED, "Using null as an array offset is deprecated, use an empty string instead");
     			result = zend_hash_update(ht, ZSTR_EMPTY_ALLOC(), value);
     			break;
     		case IS_RESOURCE:
    diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
    index b51a519533c..e31bc28affa 100644
    --- a/Zend/zend_compile.c
    +++ b/Zend/zend_compile.c
    @@ -10259,13 +10259,14 @@ static bool zend_try_ct_eval_array(zval *result, zend_ast *ast) /* {{{ */
     					zend_hash_index_update(Z_ARRVAL_P(result), 1, value);
     					break;
     				case IS_NULL:
    -					zend_hash_update(Z_ARRVAL_P(result), ZSTR_EMPTY_ALLOC(), value);
    -					break;
    +					/* Null key will generate a warning at run-time. */
    +					goto fail;
     				default:
     					zend_error_noreturn(E_COMPILE_ERROR, "Illegal offset type");
     					break;
     			}
     		} else if (!zend_hash_next_index_insert(Z_ARRVAL_P(result), value)) {
    +fail:
     			zval_ptr_dtor_nogc(value);
     			zval_ptr_dtor(result);
     			return 0;
    diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
    index 3c3065e1568..4e4a7cfdf78 100644
    --- a/Zend/zend_vm_def.h
    +++ b/Zend/zend_vm_def.h
    @@ -6278,7 +6278,11 @@ ZEND_VM_C_LABEL(num_index):
     		} else if ((OP2_TYPE & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) {
     			offset = Z_REFVAL_P(offset);
     			ZEND_VM_C_GOTO(add_again);
    -		} else if (Z_TYPE_P(offset) == IS_NULL) {
    +		} else if (UNEXPECTED(Z_TYPE_P(offset) == IS_NULL)) {
    +			zend_error(E_DEPRECATED, "Using null as an array offset is deprecated, use an empty string instead");
    +			if (UNEXPECTED(EG(exception))) {
    +				HANDLE_EXCEPTION();
    +			}
     			str = ZSTR_EMPTY_ALLOC();
     			ZEND_VM_C_GOTO(str_index);
     		} else if (Z_TYPE_P(offset) == IS_DOUBLE) {
    diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
    index 50870ce463d..fc8735de516 100644
    Binary files a/Zend/zend_vm_execute.h and b/Zend/zend_vm_execute.h differ
    
  2. added a commit that references this issue on Oct 29, 2025
    0df31e5
  3. thomas-0816 commented on Oct 29, 2025

    @thomas-0816
    Author

    Thank You!

  4. justin-oh commented on Sep 22, 2026

    @justin-oh

    I can get why $array[null => 'value']; can be deprecated but why $a[null]?

    PHP is full of code like this:

    $keyThatMightBeNull;
    
    $arrayWithKeys;
    
    $value = $arrayWithKeys[$keyThatMightBeNull] ?? null;

    You should handle $a[null] the same way you would handle $a['key_that_doesnt_exist']. Otherwise, 1000s of people each have to go and update dozens of lines of code to be like this:

    $keyThatMightBeNull;
    
    $arrayWithKeys;
    
    $value = null;
    
    if (null !== $keyThatMightBeNull) {
        $value = $arrayWithKeys[$keyThatMightBeNull] ?? null;
    }

    It seems so unnecessary when it can be handled directly in the source code intuitively.

    Further, the "use an empty string instead" part of the error message is misleading in the case of trying to access $a[null]. The error is suggesting they try accessing $a[''] which could also not exist or provide an incorrect value in that context.

  5. justin-oh commented on Sep 22, 2026

    @justin-oh

    Basically I would expect these scenarios:

    1. $array[$nonNullKeyThatDoesntExist] and $array[$nullKeyThatDoesntExist] behave the same; this is intuitive
    2. $array[$nonNullKeyThatExists] functions as usual
    3. $array[$nullKeyThatExists] functions as usual; only show deprecation for $array[null => 'value'] and in a future version of PHP throw an error for $array[null => 'value']; when the developer is forced to remove their null key, point 1 handles $array[$nullKeyThatDoesntExist]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions