>  while declaring an object falsifiable changes the semantics of `if ($obj)`.

Got me thinking, appreciate that. 

In PHP: 

false == empty == null 

(Originally the RFC was geared toward data objects being able to natively 
express emptiness when passed to empty() - might be beneficial for the 
SimpleXMLElement topic - conversation revealed false and empty are tightly 
coupled in PHP and the preference was to declare false instead of empty to 
cover more use cases, including empty().)

I’m not sure how much if ($obj) actually changes what we do at a fundamental 
level - due to the true/false (null) caveat:

> Today, in a common situation where I know that a variable contains either an 
> object of some type or null […]
> unless I have carefully read the docs in order to know whether they have opt 
> into that misfeature, and, if so, what exactly “falsy” means for them 
> (invalid?, empty?, zero?).

Do you still find yourself reading the docs or debugging to figure out why the 
thing became null instead of another, expected type (I do)?

Asked in a different way, with or without the RFC, I still need to know if the 
implementer has opted in to using null (or empty or false), and under what 
circumstances I would receive null (or empty or false), yeah?

Default behavior is:

if (new MyType($config)) {
  // do something with not null - true - can’t use object because unassigned

}
// code is unreachable because instances always resolve to true unless an 
exception occurs during initialization; the guard is somewhat unnecessary.

Given the default behavior, would we consider a static initializer returning 
MyType|null or ?MyType to be an anti-pattern (“misfeature”), because 
__construct can’t return null (or false or anything else really)?

We could also throw during initialization; see Stripe: 
https://github.com/stripe/stripe-php/blob/9c7e27ab7e30a50853aa23139d581ceab3b5e619/lib/BaseStripeClient.php#L254

try {
  $obj = new MyType($config);

  // handle true - able to use $obj

} catch ($e) {
  // handle false - object type we interact with is the error - not null and 
not $obj, but still represents false because the unintended occurred

}

Or we could opt in to returning the instance of the error directly, without 
throwing: MyType|MyError - user would still need to guard in some way - using 
is_a() possibly - see null objects: 
https://en.wikipedia.org/wiki/Null_object_pattern

Shifting gears.

Consider a situation where there’s a range of true and false within the object 
definition; PSR-7 responses due to HTTP status codes: 
https://www.php-fig.org/psr/psr-7/ and 
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status

if ($response->getStatusCode() > 199 and $response->getStatusCode() < 300) {
  // do something with “true” - which has a range of 100 possibilities at a 
granular level, which we could respond to differently - possible to interact 
with $response

}
// do something with “false” - which has a range of more than 100 possibilities 
at a granular level, which we could respond to differently - possible to 
interact with $response

We might wrap response to create an isOk() method to move the conditional logic 
somewhere within the object itself and make the call site more readable.

If ($response->isOk()) {
  // do something with “true” - still able to interact with $response

}
// do something with “false” - still able to interact with $response

With the RFC:

if (new MyType($config)) {
  // do something with “true” - can’t use MyType because not assigned

}
// reachable because possible to resolve to false - if implements Falsifiable 
and __toBool can resolve to false - can’t use MyType because not assigned

if ($response = new MyType($config)) {
  // do something with “true” - with the option of using $response

}
// reachable - can’t use MyType because may not be assigned

$response = new MyType($config);
If ($response) {
  // do something with “true” - with the option of using $response

}
// do something with “false” - with the option of using $response

Or a host of other possibilities for the conditional guard.

They’re all shorthands for the same, high-level concept - does this thing 
resolve to true *or* false (null), yeah?

Cheers,
Josh

ps. 

Added open issue of what to do with callable: 
https://wiki.php.net/rfc/objects-can-be-falsifiable - specifically in the 
context of __invoke()

Added invokable RFC to RFC list.

> On Nov 3, 2022, at 4:30 AM, Claude Pache <claude.pa...@gmail.com> wrote:
> 
> 
> 
>> Le 3 nov. 2022 à 02:51, Josh Bruce <j...@joshbruce.dev 
>> <mailto:j...@joshbruce.dev>> a écrit :
>> 
>> Similar to:
>> 
>> function x(?SomeType $arg): ?SomeOtherType
>> 
>> Instead of:
>> 
>> function x(SomeType|null $arg): SomeType|null
>> 
>> Both are the same thing under the hood.
> 
> The big difference between `?SomeType` shortcut and falsifiable object 
> feature, is that using `?SomeType` does not change the semantics of 
> `SomeType|null`, while declaring an object falsifiable changes the semantics 
> of `if ($obj)`.
> 
>> 
>> In that same spirit, totally opt-in - not required to use or you can get 
>> there some other way - similar to Marco’s comment around __toString. 
> 
> 
> 
> It is opt-in for the implementer of the library, not for the user of it, as 
> they are not necessarily the same person.
> 
> Today, in a common situation where I know that a variable contains either an 
> object of some type or null, I routinely write `if ($a)` to mean `if ($a !== 
> null)`. Now, with magic `__toBool()` possibly implemented by  the library’s 
> author, I don’t know what `if ($a)` means, unless I have carefully read the 
> docs in order to know whether they have opt into that misfeature, and, if so, 
> what exactly “falsy” means for them (invalid?, empty?, zero?). Ironically, 
> that will incite me to not use anymore the implicit casting to bool for 
> objects, and to write systematically `if ($a !== null)`, so that both myself 
> and the reader of my code won’t have to look at the docs in order to 
> understand such simple code.
> 
> —Claude

Reply via email to