# Deprecated partially supported callables: should is_callable() throw a deprecation notice ?

- Thread: https://externals.io/message/117342
- Original archive: https://news-web.php.net/php.internals/117342
- Started by Juliette Reinders Folmer on 2022-03-16T05:52:01+00:00
- Messages: 13
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #117342 — Juliette Reinders Folmer — 2022-03-16T05:52:01+00:00

URL: https://externals.io/message/117342#117342

L.s.,

I've just been looking in detail at the Partially Supported Callables 
deprecation RFC: 
https://wiki.php.net/rfc/deprecate_partially_supported_callables

The RFC explicitly excludes the `is_callable()` function and the 
`callable` type from throwing deprecation notices.

> The |is_callable()| function and |callable| type remain side-effect 
> free and do not throw a deprecation warning. They will continue to 
> accept these callables until support is removed entirely. 

While I can fully support this for the `callable` type, I wonder if the 
decision to not throw a deprecation on use in `is_callable()` is the 
right one (though I understand the desire to keep it side-effect free).

Consider these code samples:

   function foo(callable $callback) {}
   foo('static::method');

This function call not throwing a deprecation is not problematic as in 
PHP 9.0 the function will start throwing a TypeError.

   if (is_callable('static::method')) {
       static::method();
   }

The second code sample, however, is problematic, as in PHP 9.0, the 
behaviour of this code will be silently reversed for those callbacks 
which would previously result in `is_callable()` returning true, which 
makes this a potentially dangerous change without deprecation notice.

Would anyone care to enlighten me as to whether this was given due 
consideration ?

Smile,
Juliette

---

## #117344 — Christian Schneider — 2022-03-16T08:57:04+00:00

URL: https://externals.io/message/117342#117344 · In reply to: #117342

Am 16.03.2022 um 06:52 schrieb Juliette Reinders Folmer <php-internals_nospam@adviesenzo.nl>:
> I've just been looking in detail at the Partially Supported Callables deprecation RFC: https://wiki.php.net/rfc/deprecate_partially_supported_callables
> 
> The RFC explicitly excludes the `is_callable()` function and the `callable` type from throwing deprecation notices.
> 
> > […]
> 
> While I can fully support this for the `callable` type, I wonder if the decision to not throw a deprecation on use in `is_callable()` is the right one (though I understand the desire to keep it side-effect free).
> 
> Consider these code samples:
> 
>  function foo(callable $callback) {}
>  foo('static::method');
> 
> This function call not throwing a deprecation is not problematic as in PHP 9.0 the function will start throwing a TypeError.

My reaction to your last sentence is actually quite the opposite: This is a major problem because code which was "just working" directly goes to a TypeError without a migration phase warning about it. This is something I've repeatedly advocated against.

>  if (is_callable('static::method')) {
>      static::method();
>  }
> 
> The second code sample, however, is problematic, as in PHP 9.0, the behaviour of this code will be silently reversed for those callbacks which would previously result in `is_callable()` returning true, which makes this a potentially dangerous change without deprecation notice.

I agree with you here: Code which silently changes behavior is also a migration hassle.

- Chris

---

## #117371 — Christoph M. Becker — 2022-03-18T13:37:36+00:00

URL: https://externals.io/message/117342#117371 · In reply to: #117342

> I've just been looking in detail at the Partially Supported Callables
> deprecation RFC:
> https://wiki.php.net/rfc/deprecate_partially_supported_callables
>
> The RFC explicitly excludes the `is_callable()` function and the
> `callable` type from throwing deprecation notices.
>
> > […]
>
> While I can fully support this for the `callable` type, I wonder if the
> decision to not throw a deprecation on use in `is_callable()` is the
> right one (though I understand the desire to keep it side-effect free).
>
> Consider these code samples:
>
>   function foo(callable $callback) {}
>   foo('static::method');
>
> This function call not throwing a deprecation is not problematic as in
> PHP 9.0 the function will start throwing a TypeError.
>
>   if (is_callable('static::method')) {
>       static::method();
>   }
>
> The second code sample, however, is problematic, as in PHP 9.0, the
> behaviour of this code will be silently reversed for those callbacks
> which would previously result in `is_callable()` returning true, which
> makes this a potentially dangerous change without deprecation notice.
>
> Would anyone care to enlighten me as to whether this was given due
> consideration ?

Frankly, I don't know.  Apparently, there was almost no discussion about
that RFC.  Part of the reasoning to not raise `E_DEPRECATED` when calling
 `is_callable()` was likely the typical use case

  $callable = …;
  if (is_callable($callable)) {
      call_user_func($callable);
  }

what would report the deprecation when actually calling the callable.
Not sure what to do regarding your given use case(s).

--
Christoph M. Becker

---

## #117374 — Juliette Reinders Folmer — 2022-03-18T17:03:59+00:00

URL: https://externals.io/message/117342#117374 · In reply to: #117371

> 
> > […]
> Frankly, I don't know.  Apparently, there was almost no discussion about
> that RFC.  Part of the reasoning to not raise `E_DEPRECATED` when calling
> `is_callable()` was likely the typical use case
>    $callable = …;
>    if (is_callable($callable)) {
>        call_user_func($callable);
>    }
> what would report the deprecation when actually calling the callable.
> Not sure what to do regarding your given use case(s).
> --
> Christoph M. Becker

Unfortunately, those aren't the only places I see this happening and my 
example is not a stand-alone case either.

I came across the above code sample ( is_callable('static::method') / 
is_callable(['parent', __FUNCTION__]) with variable method calls) in a 
number of different packages when testing a (PHPCompatibility) sniff I 
am writing to detect the deprecation, so this code pattern looks to be 
relatively common.

Some examples:
* 
https://github.com/symfony/service-contracts/blob/bc0a2247c72d29241b5a06fb60dc1c9d9acf2a3a/ServiceSubscriberTrait.php#L39
* 
https://github.com/mockery/mockery/blob/c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac/library/Mockery/Mock.php#L960
* 
https://github.com/simplepie/simplepie/blob/dacf0ed495d2e8fb306e526ca3f2a846af78a7c9/tests/oldtests/absolutize/RFC3986.5.4/base.php#L13

---

## #117544 — Claude Pache — 2022-04-19T15:18:11+00:00

URL: https://externals.io/message/117342#117544 · In reply to: #117374

> Le 18 mars 2022 à 18:03, Juliette Reinders Folmer <php-internals_nospam@adviesenzo.nl> a écrit :
> 
> 
> > […]
> 
> Unfortunately, those aren't the only places I see this happening and my example is not a stand-alone case either.
> 
> I came across the above code sample ( is_callable('static::method') / is_callable(['parent', __FUNCTION__]) with variable method calls) in a number of different packages when testing a (PHPCompatibility) sniff I am writing to detect the deprecation, so this code pattern looks to be relatively common.
> 
> Some examples:
> * https://github.com/symfony/service-contracts/blob/bc0a2247c72d29241b5a06fb60dc1c9d9acf2a3a/ServiceSubscriberTrait.php#L39
> * https://github.com/mockery/mockery/blob/c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac/library/Mockery/Mock.php#L960
> * https://github.com/simplepie/simplepie/blob/dacf0ed495d2e8fb306e526ca3f2a846af78a7c9/tests/oldtests/absolutize/RFC3986.5.4/base.php#L13
> 

As I think that it is a serious oversight of the RFC, I have open:

https://github.com/php/php-src/issues/8401 <https://github.com/php/php-src/issues/8401>

—Claude

---

## #117545 — Andreas Hennings — 2022-04-19T18:20:21+00:00

URL: https://externals.io/message/117342#117545 · In reply to: #117544

A deprecation warning on `is_callable()` would imply that in a future
version of PHP that call will be illegal.
But this is not the case.
 `is_callable()` is meant to accept any value, and return true or false.
is_callable('static::method') will still be a valid call in future
versions, only the result will be different.
This change simply cannot be covered with a deprecation warning.
Developers have to update their code when they upgrade to PHP 9.x, or
they need a `PHP_VERSION_ID` check, as sad as that is.

One solution could be to accept an additional parameter to enable the
forward behavior for `is_callable()`.
But then this parameter would have to be removed again later.
Also, `is_callable()` already has two extra parameters, so it would get crowded.

-- Andreas


>
>
>
> > […]
>
> As I think that it is a serious oversight of the RFC, I have open:
>
> https://github.com/php/php-src/issues/8401 <https://github.com/php/php-src/issues/8401>
>
> —Claude

---

## #117655 — Guilliam Xavier — 2022-05-02T11:14:47+00:00

URL: https://externals.io/message/117342#117655 · In reply to: #117344

On Wed, Mar 16, 2022 at 9:57 AM Christian Schneider
<cschneid@cschneid.com> wrote:
>
> Am 16.03.2022 um 06:52 schrieb Juliette Reinders Folmer <php-internals_nospam@adviesenzo.nl>:
> > […]
>
> [...] This is a major problem because code which was "just working" directly goes to a TypeError without a migration phase warning about it. This is something I've repeatedly advocated against.
>
> > […]
>
> I agree with you here: Code which silently changes behavior is also a migration hassle.

Hi,

I too would rather have "extra" deprecation notices in 8.2 than
*sudden errors / silent behavior changes* in 9.0 (for the callable
type declaration / the `is_callable()` function)...

Regards,

-- 
Guilliam Xavier

---

## #117656 — drealecs@gmail.com — 2022-05-02T11:56:42+00:00

URL: https://externals.io/message/117342#117656 · In reply to: #117655

On Mon, May 2, 2022 at 2:15 PM Guilliam Xavier <guilliam.xavier@gmail.com>
wrote:

> On Wed, Mar 16, 2022 at 9:57 AM Christian Schneider
> <cschneid@cschneid.com> wrote:
> > […]
> php-internals_nospam@adviesenzo.nl>:
> > […]
> deprecation RFC:
> https://wiki.php.net/rfc/deprecate_partially_supported_callables
> > […]
> `callable` type from throwing deprecation notices.
> > […]
> understand the desire to keep [them] side-effect free).
> > […]
> directly goes to a TypeError without a migration phase warning about it.
> This is something I've repeatedly advocated against.
> > […]
> for those callbacks which would previously result in `is_callable()`
> returning true, which makes this a potentially dangerous change without
> deprecation notice.
> > […]
> migration hassle.
>
> Hi,
>
> I too would rather have "extra" deprecation notices in 8.2 than
> *sudden errors / silent behavior changes* in 9.0 (for the callable
> type declaration / the `is_callable()` function)...
>
>
>
The point is that this is not an usual deprecation, something that will
change to an error in the future.
In the end, it's just a change in behavior with no error before or after.
It does not fit the "deprecation".

As I dealt with several PHP upgrades, and I'm still going to deal with them
in the future, I proposed an idea at some point on the mailing list:
To find a way to "flag" a behavior change.
The problem is that it should be something outside E_ALL, or to be able to
handle it in a totally different way than setting an error handler for it.
Something that can be used when preparing for an PHP upgrade, something
like:
register_version_change_handler(callable $callback, $toPhpVersion)
While preparing for a future runtime version, it would be deployed to
production and would record behavioral changes in a logging system.
These changes need to be manually reviewed, of course, as a change can be
to return false instead of null and if that value would be used further in
an if.
When using in a library maybe there could be some token like @@@ to not
trigger the handler.

So yeah, this cannot really be an usual deprecation, it needs to be
something else, IMO.

Regards,
Alex

---

## #117657 — Andreas Leathley — 2022-05-02T12:28:08+00:00

URL: https://externals.io/message/117342#117657 · In reply to: #117656

> The point is that this is not an usual deprecation, something that will
> change to an error in the future.
> In the end, it's just a change in behavior with no error before or after.
> It does not fit the "deprecation".
>
> As I dealt with several PHP upgrades, and I'm still going to deal with them
> in the future, I proposed an idea at some point on the mailing list:
> To find a way to "flag" a behavior change.
> The problem is that it should be something outside E_ALL, or to be able to
> handle it in a totally different way than setting an error handler for it.
> Something that can be used when preparing for an PHP upgrade, something
> like:
> register_version_change_handler(callable $callback, $toPhpVersion)
> While preparing for a future runtime version, it would be deployed to
> production and would record behavioral changes in a logging system.
> These changes need to be manually reviewed, of course, as a change can be
> to return false instead of null and if that value would be used further in
> an if.
> When using in a library maybe there could be some token like @@@ to not
> trigger the handler.

To me, adding another error/change handler system seems like a bad way
of dealing with this, not to mention that you need to introduce this
behavior in a PHP version first before anyone can then use it, so it
would be years before this could be useful, and even more years until
there would be established patterns and enough knowledge around it.
Using the existing way of notices, warnings and errors seems just as
good to me, and people have been handling those for many years. Any new
way of dealing with this should have a very high bar to clear and would
need to prove a huge gain in value.

To me this does fit the definition of a deprecation - behavior for the
given code will change intentionally in a future version, and you are
warning about that future change. But one could also emit a notice, or a
warning. The advantage of deprecation notices is that many tools ignore
those specifically, as they are something to look at, but not
necessarily right at this moment. Being more lenient in certain
situations with deprecation notices is therefore a good pattern for
easier adoption of new versions. I have included that behavior in my own
applications, by logging deprecations separately - I will check them out
once in a while, but they do not need immediate attention. Any other PHP
notice or warning would warrant immediate attention, as it would be
unexpected.

---

## #117658 — Guilliam Xavier — 2022-05-02T12:36:58+00:00

URL: https://externals.io/message/117342#117658 · In reply to: #117656

> 
> > […]
>
> The point is that this is not an usual deprecation, something that will change to an error in the future.
> In the end, it's just a change in behavior with no error before or after. It does not fit the "deprecation".

This has already been said earlier, and answered:


> > […]
>
> No,  in the case of `is_callable()`, the deprecation warning will imply that, in a future version of PHP, the behaviour will change.  There are precedents of deprecation warning for changing behaviour: https://3v4l.org/Iqo4N

Regards,

-- 
Guilliam Xavier

---

## #117659 — Rowan Tommins — 2022-05-02T12:43:02+00:00

URL: https://externals.io/message/117342#117659 · In reply to: #117656

> The point is that this is not an usual deprecation, something that will
> change to an error in the future.
> In the end, it's just a change in behavior with no error before or after.
> It does not fit the "deprecation".


That was my instinct too, but Juliette's analysis elsewhere on the 
thread has convinced me that it is very likely that anyone seeing this 
deprecation would want to change their code, rather than allowing it to 
change behaviour in 9.0.

Perhaps a reasonable comparison is the change in precedence of the 
concatenation operator 
[https://wiki.php.net/rfc/concatenation_precedence]. The expression 
("sum: " . $a + $b) is valid in PHP 7 and PHP 8, but has different 
results; PHP 7.4 included a deprecation notice because users would 
almost certainly want to choose which behaviour they wanted.

The difference in this case is that the code change probably won't 
happen in the same place as the `is_callable()` function call, but 
somewhere further up the stack. For instance, a framework function might 
accept arbitrary values and test them with is_callable; an application 
using that framework might pass "parent::foo", which will change 
behaviour in 9.0. It is the application's author who will benefit from 
the deprecation notice, so they can pass a different value whose 
behaviour isn't going to change.

Regards,

-- 
Rowan Tommins
[IMSoP]

---

## #117669 — Juliette Reinders Folmer — 2022-05-05T05:37:22+00:00

URL: https://externals.io/message/117342#117669 · In reply to: #117659

> 
> > […]
>
>
> That was my instinct too, but Juliette's analysis elsewhere on the 
> thread has convinced me that it is very likely that anyone seeing this 
> deprecation would want to change their code, rather than allowing it 
> to change behaviour in 9.0.
>
> Perhaps a reasonable comparison is the change in precedence of the 
> concatenation operator 
> [https://wiki.php.net/rfc/concatenation_precedence]. The expression 
> ("sum: " . $a + $b) is valid in PHP 7 and PHP 8, but has different 
> results; PHP 7.4 included a deprecation notice because users would 
> almost certainly want to choose which behaviour they wanted.
>
> The difference in this case is that the code change probably won't 
> happen in the same place as the `is_callable()` function call, but 
> somewhere further up the stack. For instance, a framework function 
> might accept arbitrary values and test them with is_callable; an 
> application using that framework might pass "parent::foo", which will 
> change behaviour in 9.0. It is the application's author who will 
> benefit from the deprecation notice, so they can pass a different 
> value whose behaviour isn't going to change.
>

I was going to mention the same example as the precedent ;-)

Either way, how will this get picked up now ? What are the next steps to 
put this thing in motion ?

(Note: please don't suggest for me to create a PR - I'm a PHP dev, not a 
C-dev)

---

## #117670 — Rowan Tommins — 2022-05-05T13:37:42+00:00

URL: https://externals.io/message/117342#117670 · In reply to: #117669

> I was going to mention the same example as the precedent ;-) 


Another relevant precedent, since the original RFC talked about keeping 
things "side-effect free", is that passing "99 red balloons" to an "int" 
parameter raised an `E_NOTICE` in PHP 7.x, so a "callable" parameter 
raising an `E_DEPRECATED` should be fine.


> Either way, how will this get picked up now ? What are the next steps 
> to put this thing in motion ?
>
> (Note: please don't suggest for me to create a PR - I'm a PHP dev, not 
> a C-dev)


If nobody else does, I can have a look at the implementation, which I 
think should be fairly straight-forward.

However, since the current behaviour was explicitly mentioned in the 
accepted RFC, and the response here wasn't immediately unanimous, it's 
probably sensible to write it up as its own RFC to be voted on. It 
doesn't need to say much, just summarise the key points from this 
discussion. See https://wiki.php.net/rfc/howto for the steps, and feel 
free to send me a message off-list if you want a hand drafting or 
proof-reading it.

Regards,

-- 
Rowan Tommins
[IMSoP]
