17

While testing if my script is compatible, I got stuck on the following code:

function getDir($a, $o = 2) {
    $d = Floor($a / $o);
    return ($d % 2 === 0);
}

Prior to , this worked fine, however, on it throws:

Fatal error: Cannot redeclare getDir()

3v4l.org


After searching for a while, I've found that introduced a new alias to dir():

/** @param resource $context */
function getdir(string $directory, $context = null): Directory|false {}

php-src line 709

Questions

  • Can I get my code to work on without renaming the function?
  • Is there a list of all new function aliases?
0stone0
  • 34,288
  • 4
  • 39
  • 64
  • 2
    The `getdir` alias seems to be very old (< PHP 5) https://github.com/php/php-src/blob/php-4.4.9/ext/standard/basic_functions.c#L714. I dont see any notices in the php migration guide regarding function redeclaration, maybe thats a bug that was fixed with PHP 8 – thehennyy Mar 29 '21 at 13:15
  • 1
    The merge of the PHP 8 branch didn't introduce this. https://github.com/php/php-src/commit/bfbac70ec5d2380adbf88211da313554d0058af9#diff-96c05a0551ccbeca3ff610c111f88b54a0d727888cf353f477ea8dc951a18637 – ADyson Mar 29 '21 at 13:16
  • 3
    @ADyson Well, *something did*: https://3v4l.org/Zgork – deceze Mar 29 '21 at 13:18
  • @deceze I didn't doubt it. But the code itself referenced by the OP has been there a long time, that's all I was saying. Perhaps there is some other non-obvious change which made it become active in some way - I don't claim to understand how PHP is actually built. From a read of the release notes and the list of incompatible changes in 8.0 I can't see anything which looks like it would be a root cause. But it must be there in some form, somewhere. – ADyson Mar 29 '21 at 13:25
  • 2
    `Can I get my code to work on php-8 without renaming the function`...not unless you change its scope (e.g. put it inside a class, perhaps). – ADyson Mar 29 '21 at 13:25
  • 1
    I'm not smart enough to read through the PHP compiler code completely, but [this bug](https://bugs.php.net/bug.php?id=79382) references [this patch](https://github.com/php/php-src/commit/53eee290b6f5ca531aef19885a392c939013ce36) which targeted PHP 8, and in `function_exists` I can see a call to `zend_string_tolower`, and I'm wondering if that is related to this. A simpler test for the OP's problem is https://3v4l.org/P4lea – Chris Haas Mar 29 '21 at 13:33
  • Yeah, reading through that code it appears that `zend_hash_exists` is just called with a lowercase version of the function's name, and the hash table probably includes functions and aliases. – Chris Haas Mar 29 '21 at 13:50
  • 2
    I submitted this to the [PHP bug database](https://bugs.php.net/bug.php?id=80914). Although I do agree that naming a user-land function the same as a built-in function is asking for trouble, doing the same with an undocumented (as far as I can tell) alias shouldn't be expected to break. I think that all function aliases that core supports should be listed in the documentation at a minimum, even on a "function aliases please don't use page". It would be a bonus if the testing function could report it as an alias, but I don't think the engine is able to differentiate at that point. – Chris Haas Mar 29 '21 at 15:49
  • 3
    The plot thickens: it's not a case-sensitivity issue, the function *really didn't exist*: https://3v4l.org/dCpeh And yet it was there in the source all along. Something seriously weird has happened here. – IMSoP Apr 06 '21 at 20:11
  • 2
    I could have sworn that I tested `function_exists('getdir')` but I must have done it locally in PHP 8 only. Thanks for looking into this further @IMSoP – Chris Haas Apr 06 '21 at 20:17
  • 1
    Since you were interested in preserving the timeline, I've added a small section to the bottom of my answer. – IMSoP Apr 07 '21 at 15:16
  • 1
    Update **Monday, 12 April 2021**: Many thanks to @IMSoP for creating a [pull request](https://github.com/php/php-src/pull/6855/files) regarding this 'bug'. – 0stone0 Apr 12 '21 at 12:21

2 Answers2

14

Short answer: Whoopsie

Long answer: https://externals.io/message/113982

At the moment, I would plan on this being gone by 8.0.5 Sorry for the break, and thanks to Chris for filing a bug report abut this: https://bugs.php.net/bug.php?id=80914

Sara
  • 719
  • 5
  • 8
11

This turned out to be a lot more interesting than I expected.

The short answer is that getdir() really is new in PHP 8.0.0, but this was a mistake, and it will probably be removed in 8.0.4 or 8.0.5.

The interesting part is that getdir() is not in fact an alias, but the real name of the function internally; it's just that until 8.0, it was only accessible via its alias, dir(). To explain that, we have to go back more than 20 years...


The dir() function was added in PHP 3.0. For whatever reason - perhaps a last-minute change of name - the C function that implemented it was called "php3_getdir" not "php3_dir". That didn't matter, because every function name was mapped explicitly, like this:

function_entry php3_dir_functions[] = {
    {"opendir",     php3_opendir,   NULL},
    {"closedir",    php3_closedir,  NULL},
    {"chdir",       php3_chdir,     NULL},
    {"rewinddir",   php3_rewinddir, NULL},
    {"readdir",     php3_readdir,   NULL},
    {"dir",         php3_getdir,    NULL},
    {NULL, NULL, NULL}
};

Not long after, PHP 4 came along, and function definitions moved to using macros to match the C name to the PHP name. Since the name of the function and implementation didn't match, "dir" ended up labelled as an "alias"; but no extra entry was added for "getdir":

static zend_function_entry php_dir_functions[] = {
    PHP_FE(opendir,     NULL)
    PHP_FE(closedir,    NULL)
    PHP_FE(chdir,       NULL)
    PHP_FE(rewinddir,   NULL)
    PHP_FE(readdir,     NULL)
    PHP_FALIAS(dir,     getdir, NULL)
    {NULL, NULL, NULL}
};

An alias without a target doesn't really make sense (and there was a PHP_NAMED_FE macro for just this purpose) but it worked, so I guess nobody noticed.

In fact, through all the changes of PHP 5 and PHP 7, it carried on working, with basically the same line of C code right up to 7.4.

PHP_FALIAS(dir,  getdir, arginfo_dir)

During the work on PHP 8, however, a system was built to generate internal function information from PHP "stubs". As part of this stubs were added for all function aliases, and getdir() ended up with its own stub:

/** @param resource $context */
function getdir(string $directory, $context = null): Directory|false {}

/**
 * @param resource|null $context
 * @alias getdir
 */
function dir(string $directory, $context = null): Directory|false {}

This was then used to re-generate the C definitions, and finally getdir() had its own function entry

ZEND_FE(getdir, arginfo_getdir)
ZEND_FALIAS(dir, getdir, arginfo_dir)

This caused getdir() to be a real built-in function name, and meant you couldn't have a function with the same name.


From there, four things have happened:

  • 29th March 2021: 0stone0 posted this question.
  • Chris Haas tried to hunt down the issue, and opened a bug on the PHP bug tracker, thinking that the issue related to case sensitivity. They also confirmed that getdir() was the only alias in basic_functions.stub.php not in the manual.
  • 6th April 2021: Thinking I was going to make a quick documentation fix, I got interested, and disappeared down the above rabbit hole. I posted my findings to the PHP internals mailing list and went to bed an hour later than I intended.
  • Sara Golemon (one of the PHP 8.0 Release Managers) replied agreeing that it should be treated as a bug and reverted in the next 8.0.x release. She also posted an answer here since I hadn't yet.
IMSoP
  • 89,526
  • 13
  • 117
  • 169