php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #78635 [Arrow function] In combination with a "substr" call it returns empty string
Submitted: 2019-10-04 20:44 UTC Modified: 2019-10-04 21:29 UTC
From: php-0919 at jan-runte dot de Assigned:
Status: Not a bug Package: Scripting Engine problem
PHP Version: 7.4.0RC3 OS: macOS 10.14.6
Private report: No CVE-ID: None
 [2019-10-04 20:44 UTC] php-0919 at jan-runte dot de
Description:
------------
Installation:
phpbrew install 7.4.0RC3 +neutral +mbstring


To implement the string functions in a generically manner the arrow function is the way we go.

The test script demonstrate the misbehavior of the arrow function if it uses "substr". Without the use of arrow function all is fine. By "mb_substr" in the other hand we have no problems.

It is not related to the reporting bugs:
- Bug #71648: [DE] substr('abc', 3) returns string(0) "" for php7
- Bug #68863: Inconsistent return from substr()

The arrow function has the issue.


Test script:
---------------
<?php {
		trait StringFunctionMapper
		{
			public Closure $substr;

			protected ?string $encoding = null;

			public function setSingleByte(): void
			{
				$this->encoding = null;

				$this->substr = fn (string $string, int $start, int $length = null) => substr($string, $start, $length);
			}

			public function setMultiByte(string $encoding = 'UTF-8'): void
			{
				$this->encoding = $encoding;

				$this->substr = fn (string $string, int $start, int $length = null) => mb_substr($string, $start, $length, $this->encoding);
			}
		}
		
		$temp = new class () {
			use StringFunctionMapper;
		};
		
		$temp->setSingleByte();
		
		var_dump(substr("search phrase", 6), ($temp->substr)("search phrase", 6));

		$temp->setMultiByte();

		var_dump(mb_substr("search phrase", 6), ($temp->substr)("search phrase", 6));
}

Expected result:
----------------
string(7) " phrase"
string(7) " phrase"
string(7) " phrase"
string(7) " phrase"


Actual result:
--------------
string(7) " phrase"
string(0) ""
string(7) " phrase"
string(7) " phrase"


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2019-10-04 20:57 UTC] requinix@php.net
-Package: PHP Language Specification +Package: Scripting Engine problem
 [2019-10-04 21:09 UTC] daverandom@php.net
-Status: Open +Status: Not a bug
 [2019-10-04 21:09 UTC] daverandom@php.net
Thank you for taking the time to write to us, but this is not
a bug. Please double-check the documentation available at
http://www.php.net/manual/ and the instructions on how to report
a bug at http://bugs.php.net/how-to-report.php

This is not a bug with arrow functions, strictly speaking it's the expected behaviour of substr().

Your code explicitly passes NULL to the 3rd argument of substr(), which is then cast to int and the call ends up being interpreted as substr('search phrase', 6, 0) - which then correctly returns the empty string.

https://3v4l.org/8hKAl

I have to close this as "not a bug", however I would completely agree with you if you said this is not intuitive behaviour... it is what it is, though, and it's far too late to fix it now :-/
 [2019-10-04 21:15 UTC] daverandom@php.net
See also https://3v4l.org/j1qXn

A (not pretty) work around is https://3v4l.org/Wmqru
 [2019-10-04 21:16 UTC] requinix@php.net
substr() is one of the few functions that alters its behavior based on the number of arguments.
https://github.com/php/php-src/blob/php-7.4.0RC3/ext/standard/string.c#L2388

Note the "argc > 2" checks.

To properly address that in the arrow function you can use a ternary:

  $this->substr = fn(string $string, int $start, int $length = null) =>
    func_num_args() == 2
    ? substr($string, $start)
    : substr($string, $start, $length);

Or better yet, if you're just proxying substr() then you can ignore the arguments and instead

  $this->substr = fn() => substr(...func_get_args());
 [2019-10-04 21:22 UTC] daverandom@php.net
Further to the last suggestion above, to retain type safety on the proxy it's also possible to do this:

$this->substr = fn(string $str, int ...$chunk) => substr($str, ...$chunk); 

However, it's also worth noting that simply:

$this->substr = 'substr';

...will probably do the job in that particular case :-)
 [2019-10-04 21:31 UTC] marandall@php.net
substr explicitly checks for if the number of arguments passed is 3. 

Your arrow function defines 3 parameters, and explicitly passes them all each time, thus making the function believe that the length has been set, which it has.

The length is a null that is being cast into an integer, giving you 0, so the function is returning an empty string, which is what you're asking it for.

From the docs: If length is given and is 0, FALSE or NULL, an empty string will be returned.

mb_substr works differently and it does accept a specific null.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sun May 19 02:01:35 2024 UTC