Hello Interntals,
Someone reached out to me outside of internals regarding the RFC I submitted on being able to declare objects falsifiable, so, I decided to update and re-enter the fray.
I’ve updated the references section as many of the RFCs that were under discussion at the time have since been implemented.
I still find myself in situations where having the capability would be beneficial. Specifically, I’m noticing with API responses where I want to pass the body received from a ResponseInterface the object can check itself. I currently use methods such as:
->isValid()
->isInvalid()
->toBool()
And similar - the isValid and isInvalid() methods are just aliases of toBool(), which speaks to the ability for adopters to make their interfaces compatible without breaking changes in their code.
In the case of a conditional - looks like this:
$obj = Object::init($response);
If ($obj->isValid()) {
// do something with $obj
}
Or:
If (
$obj = Object::init($response) and
$obj->isValid()
) {
// do something with $obj
}
Would like to be able to do something like:
If ($obj = Object::init($response)) {
// do something with $obj
}
As of today, the last example is always true. You wouldn’t be able to create a guard clause for the negation:
If (
$obj = Object::init($response) and
$obj === false
) {
// handle invalid object, which could include something like $obj->failed()
}
Cheers,
Josh
Hey Internals,
So, I received a message that said of the mail I should have received from internals bounced; so, this is part test and part update.
I’ve started mapping out call paths in the project that brought this concept to light.
The project, Shoop, never uses
is_bool()
- instead it always uses empty, then reverses that for:
- Booleans
- Numbers
- Arrays
- Strings
- stdClass or data-only classes
- Json strings, which it treats as #5
I essentially get standard SPL behavior when I do this.
For the object definition I can define two interfaces:
- Falsifiable
- Emptiable
The checks go like this:
- if the object implements Falsifiable: the __toBool method would be called and would return the result. (This check would be inside
is_bool()
and could use the empty() implementation)- if the object implements Emptiable: the __isempty method would be called and return the result. (This check would be inside empty())
- else: standard output from
is_bool()
or empty() would be used when passing an instance to either of those SPL functions, depending on which the user is using.Because the concepts of emptiness and falsiness are so tightly coupled, I’m wondering if it would be better to implement both with this RFC??
Otherwise, Emptiable would be a future enhancement consideration.
I’d like to hear what the rest of Internals thinks.
Next for me: I’m going to finish solidifying the Shoop project and make sure my other projects can use latest and then continue going through the tutorials from Nikita and others on doing development Internals.
Cheers,
JoshHey Tyson,
This is great! Thank you so much, sincerely.
Still slow goings, which is fine, we have at least a year. lol
Static analyzers seem to be the biggest concern to date.
Haven’t been able to get one running locally - though I’ve only spent a few minutes here and there; definitely on the list.
A use case for this sort of thing is also a concern or question. I came across Pipeline from the PHP League and saw their interruptible processor and was wondering if something like this would be helpful there - for pipeline patterns in general: https://github.com/thephpleague/pipeline/blob/master/src/InterruptibleProcessor.php
While working on another project, saw this line from the PHP array_filter docs:
"If no callback is supplied, all entries of array equal to `FALSE` (see converting to boolean) will be removed."
I’m still field testing the latest iteration of my base project, but wanted to put a working (non-internals) implementation out there (note this covers emptiness and falseness for the purposes of the project):
Tests - https://github.com/8fold/php-shoop/blob/master/tests/RfcObjectCanBeDeclaredFalsifiableTest.php
Interface - https://github.com/8fold/php-shoop/blob/master/src/FilterContracts/Interfaces/Falsifiable.php - for our purposes the efToBool would be __toBool
Default implementation - https://github.com/8fold/php-shoop/blob/master/src/Shooped.php#L216
“Type system” implementation - https://github.com/8fold/php-shoop/blob/master/src/Filter/TypeAsBoolean.php
Cheers,
JoshHi Josh,
I'd recommend looking at https://github.com/php/php-src/pull/5156 (linked to in your RFC's reference) when implementing this PR.
Additionally, I didn't see if this was brought up - PHP already has a class which is falsifiable - SimpleXMLElement.You can see the source of ext/simplexml/simplexml.c for
_IS_BOOL
static int sxe_object_cast_ex(zend_object *readobj, zval *writeobj, int type) { php_sxe_object *sxe; xmlChar *contents = NULL; xmlNodePtr node; int rv; sxe = php_sxe_fetch_object(readobj); if (type == _IS_BOOL) { node = php_sxe_get_first_node(sxe, NULL); if (node) { ZVAL_TRUE(writeobj); } else { ZVAL_BOOL(writeobj, !sxe_prop_is_empty(readobj)); } return SUCCESS; }
static php_sxe_object* php_sxe_object_new(zend_class_entry *ce, zend_function *fptr_count) intern->zo.handlers = &sxe_object_handlers; // ... elsewhere in ext/simplexml/simplexml.c sxe_object_handlers.cast_object = sxe_object_cast;
The default handlers would be overridden, so this would probably need to fall back to the original handlers for types other than bool.
Also, this might need to copy all of the handlers from the parent class entry's zend_object_handlers,
to meet the expected behavior of other cast types and other magic behaviors from SimpleXMLElement and other classes continuing to work.From the perspective of static analysis, a few things to note:
- Static analyzers such as psalm/phan currently deliberately don't bother handling the possibility that
object
can be false even after it was checked for falsiness.
There's hundreds of other things that could be implemented, and adding the special casing and performance overhead checking for FFI objects and SimpleXMLElement, subclasses of those is currently low priority compared to those things.Code using SimpleXMLElement/FFI is generally limited to a few files in practice.
Definitely possible for analyzers to support this for known base classes, though, and the priority would increase if the RFC passed.
I am a maintainer of Phan.
- For BC reasons, internal data structures such as ArrayObject probably wouldn't get changed in any php release
(e.g. could break code using falsiness check instead of null check).
So this might lead to inconsistencies with newer extensions if half of the data structures treat emptiness as false and half don't.- This feature may end up getting adopted in cases where it's convenient but turns out prone to accidental bugs and later is deprecated and removed.
For example, https://stackoverflow.com/questions/25031236/if-elem-vs-elem-is-not-none was seen in python(e.g.
if (!$this->resultSetObject) { $this->resultSetObject = slow_db_operation(); }
would not behave the way people would previously expect for most objects (executed repeatedly instead of once))<?php function test(SimpleXMLElement $e) { // False positive RedundantCondition in psalm if ($e) { } }
And I don’t know of a way to add an interface to new stdClass() - but thought this might be a valid use case:
stdClass
isn't a final class. I assume they meant thisclass EmptyStdClass extends stdClass implements Falsifiable { public function __toBool() { return false; } } function example(stdClass $s) { if (!$s) { throw new Exception("Previously impossible"); } }
Thanks,
- Tyson
--To unsubscribe, visit: https://www.php.net/unsub.php
Since this topic fits like a glove, just dropping the "why I don't use
__toString()
" argument here:
https://github.com/ShittySoft/symfony-live-berlin-2018-doctrine-tutorial/pull/3#issuecomment-460085493
A falsifiable object is worse than __toString()
in this context, IMO.
Marco Pivetta
Hello Interntals,
Someone reached out to me outside of internals regarding the RFC I
submitted on being able to declare objects falsifiable, so, I decided to
update and re-enter the fray.I’ve updated the references section as many of the RFCs that were under
discussion at the time have since been implemented.I still find myself in situations where having the capability would be
beneficial. Specifically, I’m noticing with API responses where I want to
pass the body received from a ResponseInterface the object can check
itself. I currently use methods such as:->isValid()
->isInvalid()
->toBool()And similar - the isValid and isInvalid() methods are just aliases of
toBool(), which speaks to the ability for adopters to make their interfaces
compatible without breaking changes in their code.In the case of a conditional - looks like this:
$obj = Object::init($response);
If ($obj->isValid()) {
// do something with $obj
}Or:
If (
$obj = Object::init($response) and
$obj->isValid()
) {
// do something with $obj
}Would like to be able to do something like:
If ($obj = Object::init($response)) {
// do something with $obj
}As of today, the last example is always true. You wouldn’t be able to
create a guard clause for the negation:If (
$obj = Object::init($response) and
$obj === false
) {
// handle invalid object, which could include something like
$obj->failed()
}Cheers,
JoshHey Internals,
So, I received a message that said of the mail I should have received
from internals bounced; so, this is part test and part update.I’ve started mapping out call paths in the project that brought this
concept to light.The project, Shoop, never uses
is_bool()
- instead it always uses empty,
then reverses that for:
- Booleans
- Numbers
- Arrays
- Strings
- stdClass or data-only classes
- Json strings, which it treats as #5
I essentially get standard SPL behavior when I do this.
For the object definition I can define two interfaces:
- Falsifiable
- Emptiable
The checks go like this:
- if the object implements Falsifiable: the __toBool method would be
called and would return the result. (This check would be insideis_bool()
and could use the empty() implementation)- if the object implements Emptiable: the __isempty method would be
called and return the result. (This check would be inside empty())- else: standard output from
is_bool()
or empty() would be used when
passing an instance to either of those SPL functions, depending on which
the user is using.Because the concepts of emptiness and falsiness are so tightly coupled,
I’m wondering if it would be better to implement both with this RFC??Otherwise, Emptiable would be a future enhancement consideration.
I’d like to hear what the rest of Internals thinks.
Next for me: I’m going to finish solidifying the Shoop project and make
sure my other projects can use latest and then continue going through the
tutorials from Nikita and others on doing development Internals.Cheers,
JoshOn Aug 30, 2020, at 9:32 AM, Josh Bruce <josh@joshbruce.dev <mailto:
josh@joshbruce.dev>> wrote:Hey Tyson,
This is great! Thank you so much, sincerely.
Still slow goings, which is fine, we have at least a year. lol
Static analyzers seem to be the biggest concern to date.
Haven’t been able to get one running locally - though I’ve only spent a
few minutes here and there; definitely on the list.A use case for this sort of thing is also a concern or question. I came
across Pipeline from the PHP League and saw their interruptible processor
and was wondering if something like this would be helpful there - for
pipeline patterns in general:
https://github.com/thephpleague/pipeline/blob/master/src/InterruptibleProcessor.phpWhile working on another project, saw this line from the PHP
array_filter docs:"If no callback is supplied, all entries of array equal to `FALSE`
(see converting to boolean) will be removed."
I’m still field testing the latest iteration of my base project, but
wanted to put a working (non-internals) implementation out there (note this
covers emptiness and falseness for the purposes of the project):Tests -
https://github.com/8fold/php-shoop/blob/master/tests/RfcObjectCanBeDeclaredFalsifiableTest.phpInterface -
https://github.com/8fold/php-shoop/blob/master/src/FilterContracts/Interfaces/Falsifiable.php
- for our purposes the efToBool would be __toBool
Default implementation -
https://github.com/8fold/php-shoop/blob/master/src/Shooped.php#L216“Type system” implementation -
https://github.com/8fold/php-shoop/blob/master/src/Filter/TypeAsBoolean.phpCheers,
JoshOn Aug 9, 2020, at 3:59 PM, tyson andre <tysonandre775@hotmail.com
mailto:tysonandre775@hotmail.com> wrote:Hi Josh,
I'd recommend looking at https://github.com/php/php-src/pull/5156
(linked to in your RFC's reference) when implementing this PR.
Additionally, I didn't see if this was brought up - PHP already has a
class which is falsifiable - SimpleXMLElement.You can see the source of ext/simplexml/simplexml.c for
_IS_BOOL
static int sxe_object_cast_ex(zend_object *readobj, zval *writeobj,
int type)
{
php_sxe_object *sxe;
xmlChar *contents = NULL;
xmlNodePtr node;
int rv;sxe = php_sxe_fetch_object(readobj); if (type == _IS_BOOL) { node = php_sxe_get_first_node(sxe, NULL); if (node) { ZVAL_TRUE(writeobj); } else { ZVAL_BOOL(writeobj, !sxe_prop_is_empty(readobj)); } return SUCCESS; }
static php_sxe_object* php_sxe_object_new(zend_class_entry *ce,
zend_function *fptr_count)
intern->zo.handlers = &sxe_object_handlers;
// ... elsewhere in ext/simplexml/simplexml.c
sxe_object_handlers.cast_object = sxe_object_cast;The default handlers would be overridden, so this would probably need
to fall back to the original handlers for types other than bool.
Also, this might need to copy all of the handlers from the parent
class entry's zend_object_handlers,
to meet the expected behavior of other cast types and other magic
behaviors from SimpleXMLElement and other classes continuing to work.From the perspective of static analysis, a few things to note:
- Static analyzers such as psalm/phan currently deliberately don't
bother handling the possibility thatobject
can be false even after it
was checked for falsiness.
There's hundreds of other things that could be implemented, and
adding the special casing and performance overhead checking for FFI objects
and SimpleXMLElement, subclasses of those is currently low priority
compared to those things.Code using SimpleXMLElement/FFI is generally limited to a few files
in practice.Definitely possible for analyzers to support this for known base
classes, though, and the priority would increase if the RFC passed.I am a maintainer of Phan.
- For BC reasons, internal data structures such as ArrayObject
probably wouldn't get changed in any php release
(e.g. could break code using falsiness check instead of null check).
So this might lead to inconsistencies with newer extensions if half
of the data structures treat emptiness as false and half don't.- This feature may end up getting adopted in cases where it's
convenient but turns out prone to accidental bugs and later is deprecated
and removed.
For example,
https://stackoverflow.com/questions/25031236/if-elem-vs-elem-is-not-none
was seen in python(e.g.
if (!$this->resultSetObject) { $this->resultSetObject = slow_db_operation(); }
would not behave the way people would previously expect for most
objects (executed repeatedly instead of once))<?php function test(SimpleXMLElement $e) { // False positive RedundantCondition in psalm if ($e) { } }
And I don’t know of a way to add an interface to new stdClass() - but
thought this might be a valid use case:
stdClass
isn't a final class. I assume they meant thisclass EmptyStdClass extends stdClass implements Falsifiable { public function __toBool() { return false; } } function example(stdClass $s) { if (!$s) { throw new Exception("Previously impossible"); } }
Thanks,
- Tyson
--To unsubscribe, visit: https://www.php.net/unsub.php
Hi Josh,
pon., 31 paź 2022 o 20:38 Josh Bruce josh@joshbruce.dev napisał(a):
Hello Interntals,
Someone reached out to me outside of internals regarding the RFC I
submitted on being able to declare objects falsifiable, so, I decided to
update and re-enter the fray.I’ve updated the references section as many of the RFCs that were under
discussion at the time have since been implemented.I still find myself in situations where having the capability would be
beneficial. Specifically, I’m noticing with API responses where I want to
pass the body received from a ResponseInterface the object can check
itself. I currently use methods such as:->isValid()
->isInvalid()
->toBool()And similar - the isValid and isInvalid() methods are just aliases of
toBool(), which speaks to the ability for adopters to make their interfaces
compatible without breaking changes in their code.In the case of a conditional - looks like this:
$obj = Object::init($response);
If ($obj->isValid()) {
// do something with $obj
}Or:
If (
$obj = Object::init($response) and
$obj->isValid()
) {
// do something with $obj
}Would like to be able to do something like:
If ($obj = Object::init($response)) {
// do something with $obj
}As of today, the last example is always true. You wouldn’t be able to
create a guard clause for the negation:If (
$obj = Object::init($response) and
$obj === false
) {
// handle invalid object, which could include something like
$obj->failed()
}
Have you tried not initializing invalid objects? This could help you.
Also that sounds more logical to me as I don't see any reasons to
initialize invalid objects if it's a matter of simple validation instead.
P.S. I don't see it feasible to have objects that evaluate false in
logical expressions.
Cheers,
Michał Marcin Brzuchalski
Le 2 nov. 2022 à 09:12, Michał Marcin Brzuchalski michal.brzuchalski@gmail.com a écrit :
P.S. I don't see it feasible to have objects that evaluate false in
logical expressions.
For better or for worse (IMHO, for worse), SimpleXMLElement
instances representing attributeless empty elements are already falsy, so yes, it is absolutely feasible.
—Claude
Le 2 nov. 2022 à 09:12, Michał Marcin Brzuchalski <
michal.brzuchalski@gmail.com> a écrit :P.S. I don't see it feasible to have objects that evaluate false in
logical expressions.For better or for worse (IMHO, for worse),
SimpleXMLElement
instances
representing attributeless empty elements are already falsy, so yes, it is
absolutely feasible.—Claude
This is absolutely terrifying.
Marco Pivetta
Thank you all for the engagement so far.
(Note: All scalar types and one compound type can be cast as true or false natively - of the scalar and compound types, callable and object are the only two with no, native false representation.)
Have you tried not initializing invalid objects? This could help you. Also that sounds more logical to me as I don't see any reasons to initialize invalid objects if it's a matter of simple validation instead.
I've tried valid objects (multiple approaches) - and I’m using the term valid loosely because the instances are not, technically, invalid in the strictest sense. I’m using “valid” and “invalid” as method names in the implementation because I don’t have a better word to describe the concept; still looking. If you'd like to hear more, please let me know and I can detail out other approaches, which are pretty standard.
I only used it as an example of what would be possible and trying to illustrate using a real world example of the problem-space.
With that said, the RFC isn’t meant to directly solve an implementation issue that can’t be solved any other way; it’s more syntax, style, and consistency (see note above).
Similar to:
function x(?SomeType $arg): ?SomeOtherType
Instead of:
function x(SomeType|null $arg): SomeType|null
Both are the same thing under the hood.
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.
For example, I actively avoid null and don’t use most null-related things available in PHP and tend to wrap objects, methods, and functions that might return null…but, if you’re into null, don’t let me stop you. :)
(I also tend to wrap objects, methods, and functions that return bool and some other type to put them in a context where I get one or the other, but not either-or - file_put_contents, for example…big fan of single return types.)
The one situation in which I can think of where an object might legitimately have a falsey bool casting is if it represents a single value
I see that, though I don’t necessarily agree.
To use similar language, the object could represent a single, complex concept (especially through composition) - not just a single value.
A car without a blinker is still a car and usable for most things it was designed for - and there could be a fallback for the missing blinker; valid instance with a non-blocking deficiency: https://www.dmv.org/how-to-guides/hand-signals-guide.php
P.S. I don't see it feasible to have objects that evaluate false in logical expressions.
I’m interested in more detail on this.
Feasible as in not possible to do easily? Not possible in general? Or, something else?
For better or for worse (IMHO, for worse),
SimpleXMLElement
instances representing attributeless empty elements are already falsy, so yes, it is absolutely feasible.
Interesting use case. Haven’t used SimpleXMLElement enough to comment.
Interested to hear more.
This is absolutely terrifying.
Not sure if this is referring to the SimpleXMLElement reference or beyond it. :)
Cheers,
Josh
Le 2 nov. 2022 à 09:12, Michał Marcin Brzuchalski <
michal.brzuchalski@gmail.com> a écrit :P.S. I don't see it feasible to have objects that evaluate false in
logical expressions.For better or for worse (IMHO, for worse),
SimpleXMLElement
instances
representing attributeless empty elements are already falsy, so yes, it is
absolutely feasible.—Claude
This is absolutely terrifying.
Marco Pivetta
Le 3 nov. 2022 à 02:51, Josh Bruce josh@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
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.
Le 3 nov. 2022 à 02:51, Josh Bruce <josh@joshbruce.dev mailto:josh@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 ofSomeType|null
, while declaring an object falsifiable changes the semantics ofif ($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 meanif ($a !== null)
. Now, with magic__toBool()
possibly implemented by the library’s author, I don’t know whatif ($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 systematicallyif ($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
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 $responseWe 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
This looks way much cleaner and is easy to read and understand.
While I understand the proposed feature is opt-int it introduces more magic
that can be solved using more verbose and IMO cleaner solutions.
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 assignedif ($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
This is somehow confusing, why is the $response storing object ref is ok
while inclining the new object creation is not?
This requires more attention while reading and debugging.
Cheers,
Michał Marcin Brzuchalski
While I understand the proposed feature is opt-int it introduces more magic
that can be solved using more verbose and IMO cleaner solutions.
Understood and appreciate the position.
This is somehow confusing, why is the $response storing object ref is ok
while inclining the new object creation is not?
Not quite following, apologies.
Adding a different use case.
Allow for fully-specified custom collections; array being the compound type that can resolve to false/empty based on count.
$array = [];
(bool) $array; // false
empty($array); // true
if ($array) {
// handle not empty
}
// handle empty
$array = [1, 2, 3];
(bool) $array; // true
empty($array); // false
if ($array) {
// handle not empty
}
// handle empty
Current state of PHP re custom collections (exclamation marks - ! - represent difference compared to native array):
class MyCollection implements ArrayAccess, Iterator, Countable {}
$collection = new MyCollection([]);
(bool) $collection; // true - ! - because instance exists, regardless of collection content
empty($collection); // false - ! - same reason as previous
if ($collection) {
// ! - handle empty and not empty
}
// ! - unreachable
$collection = new MyCollection([1, 2, 3]);
(bool) $collection; // true - because instance exists, regardless of collection content
empty($collection); // false - same as previous
if ($collection) {
// ! - handle empty and not empty
}
// ! - unreachable
With RFC:
class MyCollection implements ArrayAccess, Iterator, Countable, Falsifiable
{
public function __toBool(): bool
{
return $this->count() > 0;
}
}
$collection = new MyCollection([]);
(bool) $collection; // false - because collection count === 0 - close to the previous comment re single value under inspection
empty($colleciton); // true - same as previous
if ($collection) {
// handle not empty
}
// handle empty
$collection = new MyCollection([1, 2, 3]);
(bool) $collection; // true - because collection count > 0
empty($colleciton); // false - same as previous
if ($collection) {
// handle not empty
}
// handle empty
Alternative approaches for custom collection use case:
- Modify Countable (most likely, if using alternative) to include empty() and bool() methods.
Might feel more palatable despite not strictly limiting scope to the custom collection use case.
Any class with Countable, for example, would also be able to resolve to false or empty using SPL functions - including use in IF.
Allows for explicit (“verbose"), direct call of both methods by user should they choose:
$collection->empty()
$collection->bool()
Instead of $collection->__toBool() OR something like $collection->__isEmpty()
Known drawback to using Countable would be in a possible future where there is a separation between 0, false, and empty when it comes to type juggling in PHP.
Whereas using a Falsifiable interface leaves a simpler path of an Emptiness interface; again, should the three be separated in the future.
- Or, modify ArrayAccess to include the empty() and bool() methods.
This would restrict the behavior more toward the custom collection concept.
However, would lean more toward Countable due to the equivalence in PHP type juggling and Countable using an integer: 0 == false == empty
Otherwise, similar to alternative #1.
- Or, modify Iterator to include the empty() and bool() methods (or possibly leverage the valid() method that already exists?); otherwise, similar to alternative #2.
Cheers,
Josh
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 $responseWe 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 $responseThis looks way much cleaner and is easy to read and understand.
While I understand the proposed feature is opt-int it introduces more magic
that can be solved using more verbose and IMO cleaner solutions.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 assignedif ($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 $responseThis is somehow confusing, why is the $response storing object ref is ok
while inclining the new object creation is not?
This requires more attention while reading and debugging.Cheers,
Michał Marcin Brzuchalski
On Wed, Nov 2, 2022 at 1:12 AM Michał Marcin Brzuchalski <
michal.brzuchalski@gmail.com> wrote:
Hi Josh,
Have you tried not initializing invalid objects? This could help you.
Also that sounds more logical to me as I don't see any reasons to
initialize invalid objects if it's a matter of simple validation instead.P.S. I don't see it feasible to have objects that evaluate false in
logical expressions.Cheers,
Michał Marcin Brzuchalski
The one situation in which I can think of where an object might
legitimately have a falsey bool casting is if it represents a single value
(that may have multiple properties associated with it, like units of
measurement), and if ($obj)
is really asking something like if ($obj->castSingleValuePropertyToBool())
.
For most of these situations, either if ($obj->isEqual(0))
is
equivalent, or if ($obj == 0)
is equivalent if operator overloading was
accepted.
Jordan