4

I'm using PHP 7.4.16. I enabled strict_types in my PHP file thinking it would prevent passing a string argument to a function expecting an int by throwing a TypeError. However, the function actually accepts the string and coerces it to an int. But if I place a return type hint on the function it works as expected, throwing a TypeError.

This doesn't make sense to me and seems like a glaring inconsistency that can lead to bugs. Does anyone have any insight as to why this is or if I'm doing something wrong?

Test code:

<?php
declare(strict_types=1);

$ids = ['1', '2', '3'];

// No error thrown, coerces string argument to int.
array_map(fn (int $id) => $id, $ids);

// Throws PHP Fatal error:  Uncaught TypeError: Return value of {closure}() must be of the type int, string returned
array_map(fn ($id): int => $id, $ids);
Jacob
  • 2,212
  • 1
  • 12
  • 18
  • 1
    https://stackoverflow.com/q/47659587/5999372 This question I asked three years ago has the answer for you in the comments. So **strict types is determined by calling scope and Internal code is always weak** except for `call_user_func` which is converted into a direct function call as an optimization https://3v4l.org/5OGH6 – Rain Jun 13 '21 at 01:42

1 Answers1

8

strict_types only affects function calls within the file in which it's declared. From the PHP docs:

Note: Strict typing applies to function calls made from within the file with strict typing enabled, not to the functions declared within that file. If a file without strict typing enabled makes a call to a function that was defined in a file with strict typing, the caller's preference (coercive typing) will be respected, and the value will be coerced.

In your case, the examples are not calling the callback itself, they are passing it as an argument for array_map, meaning that wherever the function array_map is implemented, it has preference for coercive typing when array_map is calling your callback.

A possible solution to this would be to wrap array_map and make the call to your callback in a file in which strict_types is declared, such as this:

<?php
declare(strict_types=1);

$ids = ['1', '2', '3'];

function strict_array_map($fn, $arr){
    return array_map(fn (...$arguments) => $fn(...$arguments), $arr);
}

// Now TypeError is thrown correctly
strict_array_map(fn (int $id) => $id, $ids);

// Throws PHP Fatal error:  Uncaught TypeError: Return value of {closure}() must be of the type int, string returned
strict_array_map(fn ($id): int => $id, $ids);

https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.strict

Alex Ruiz
  • 415
  • 2
  • 8
  • "the caller's preference (coercive typing) will be respected, and the value will be coerced." The caller's preference in my case is strict typing. – Jacob Jun 08 '21 at 14:45
  • "strict_types only affects the file in which it's declared." That's not what the quote you included says. – Jacob Jun 08 '21 at 14:46
  • @deceze It's important to note that this exception on the `strict_types` is only applied on calling functions. Your second example works because is checking a return type, which this note on the docs does not apply. – Alex Ruiz Jun 08 '21 at 14:47
  • @deceze "Strict typing applies to function calls made from **within the file** with strict typing enabled", as you can see, if `srict_types` is not declared within the file in which the call to a function is made, it does not apply. Edit: I've edited the answer to be specific about being about function calls. – Alex Ruiz Jun 08 '21 at 14:51
  • I'm declaring `strict_types` and declaring and calling the functions within the same file – Jacob Jun 08 '21 at 14:53
  • 2
    @Jacob The difference here is that you are not calling the function itself. Your function is being called inside `array_map`. If you declare `$callback = fn (int $id) => $id;` and try to call it on the same file with `$callback($ids[0]);` you'll trigger `strict_types`. – Alex Ruiz Jun 08 '21 at 14:55
  • 1
    @Jacob I understand your explanation, but if the question was "How implement strict_types in array_map?" - Have you an idea? – Dri372 Jun 08 '21 at 15:07
  • @Dri372 I think the best solution could be to make a wrapper for the function, within a class or a simple function as an utility wrapper. – Alex Ruiz Jun 08 '21 at 15:09
  • 2
    @AlexRuiz should be useful to update your answer with this (ideally with a small example) – Dri372 Jun 08 '21 at 15:14
  • @Dri372 I've updated the answer to include an example of wrapper :) – Alex Ruiz Jun 08 '21 at 15:24
  • 1
    @AlexRuiz Ah, your explanation about how `array_map` is the one calling the functions helped me understand what's happening. Thank you! – Jacob Jun 08 '21 at 15:34
  • 1
    @Jacob you should consider putting array_map in your title, your question is very good, it shows how Php implements strict_types and I would think that the array_map case has been forgotten or is difficult to implement. – Dri372 Jun 08 '21 at 15:35
  • I still don't like that it works that way. Seems to violate the principle of least astonishment. – Jacob Jun 08 '21 at 15:36
  • @Jacob I tend to agree with you ... originany php is not perl, ... like windows is not linux, ... mysql not postgresl – Dri372 Jun 08 '21 at 15:39