This RFC introduces a new interface Falsifiable
and magic method __toBool()
allowing custom objects (types) to define and declare themselves true
or false
. Given PHP 8 interprets a valid instance as only true
, this would give developers (the instance itself, and other objects) the opportunity to work with an instance of a known object (type) and glean its truthiness from the instance itself, not null
or empty()
checks. (Or strlen()
and other operator-required checks.)
This RFC has 3 goals:
false
, without null
while being consistent with similar approaches as of PHP 8,Falsifiable
functional interface and __toBool()
method, which allows for direct conversion of the object by way of casting to bool
or passing to empty()
, and
Goal 1: All PHP scalar types have a true
and false
representation. One of the three compound types (array
) has a true
and false
representation. The other two compound types (callable
and object
) only have a true
representation but no false
representation. As such, developers tend to resort to using null
assigned to variables or returned from function and method calls to represent non-objects; however, this is an inaccurate representation as the object cannot be interacted with as that type of object. Further, null
itself is a special type separate from the scalar and compound types. (See Truthiness tables section.)
Language features have been added and are proposed to address or workaround this use of null
, namely nullable types (optionals) and the proposed nullsafe operator, both of which add syntax while addressing interaction with null
causing a fatal error.
This RFC seeks to add another layer. The ability to have a live instance (not null
) that can still represent false
and be interacted with as an instance of that object (type).
Goal 2: To maintain consistency and feel within PHP, this RFC follows the Countable
and Stringable
interfaces and implementations. For type safety, implementers can explicitly declare the Falsifiable
interface in their class declarations. Further, the union type bool|Falsifiable
will be dynamically added at run time to any object implementing the reserved __toBool()
method, which would allow stdClass()
objects to be defined as Falsifiable
also.
The interface stub.
interface Falsifiable { public function __toBool(): bool; }
Unlike Stringable
we can explicitly declare (force) the return type (bool
).
A __toBool()
magic method was selected to maintain consistency with PHP 8. __toString()
allows developers to specify a string representation of the object (with the Stringable
interface for type safety). There is an RFC to do similar with array
using a __toArray()
magic method. Converting to an array
can also be done by way of the __serialize()
magic method, but requires the object be passed to the serialize()
and unserialize()
functions from the standard library. The signature for __serialize()
in PHP 8 indicates the return type is being changed to an array
not a string
, but still requires being passed to the serialize()
and unserialize()
functions. Therefore, there seems to be a precedent for using magic methods when declaring base type representations of custom objects (types).
Goal 3: BC breaks should be minimal, if at all:
__construct()
replacing old style constructors in PHP 7.)as
. null
types; therefore, nullable types and the possibility of nullsafe operators can work as expected.As-is using a mixed type array:
$array = [ new stdClass(), [1, 2, 3], "", "Hello", [], new ClassWithToBoolReturningFalse() ]; $filtered = array_filter($array, function($item) { return (bool) $item }); // output: // [ // stdClass(), // [1, 2, 3], // "Hello", // ClassWithToBoolReturningFalse() // ]; $filtered = array_filter($array, function($item) { return empty($item) }); // output: // [ // "", // [] // ];
To-be with Falsifiable
interface and magic method:
$array = [ new stdClass(), [1, 2, 3], "", "Hello", [], new ClassWithToBoolReturningFalse() ]; $filtered = array_filter($array, function($item) { return (bool) $item }); // output: // [ // stdClass(), // [1, 2, 3], // "Hello" // ]; $filtered = array_filter($array, function($item) { return empty($item) }); // output: // [ // "", // [], // ClassWithToBoolReturningFalse() // ];
To get the same output from the “To-be” sample, without the Falsifiable
interface being interpreted automatically from PHP (removing the double underscore to reduce unneeded syntax).
$array = [ new stdClass(), [1, 2, 3], "", "Hello", [], new ClassWithIsEmptyReturningFalse() ]; $filtered = array_filter($array, function($item) { return (is_object($item) and $item instanceof Falsifiable) ? $item->toBool() : (bool) $item; }); // output: // [ // stdClass(), // [1, 2, 3], // "Hello" // ]; $filtered = array_filter($array, function($item) { return (is_object($item) and $item instanceof Falsifiable) ? ! $item->toBool() : empty($item); }); // output: // [ // "", // [], // ClassWithToBoolReturningFalse() // ];
Validate that the implementation can have both a true and false return, not just one or the other.
With no modifications or interventions by the developer and all types are empty (or false in the case of boolean):
Cast to | Type: | |||||||
---|---|---|---|---|---|---|---|---|
null (unset) | custom type (empty) | object | array | float | integer | string | bool(ean) | |
unset (nullify) | null | null | null | null | null | null | null | null |
custom type | error | error | error | error | error | error | error | error |
object | object (empty) | no change | no change | object | object (scalar of 0) | object (scalar of 0) | object (scalar of “”) | object (scalar of false) |
array | [] | [] | [] | no change | [0] | [0] | [“”] | [false] |
float | 0 | error | error | 0 | no change | 0 | 0 | 0 |
integer | 0 | error | error | 0 | 0 | no change | 0 | 0 |
string | “” | error | string | error | “0” | “0” | no change | “” |
boolean | false | true | true | false | false | false | false | no change |
Scalar types and their relationship to false
:
Type | Value/Context | Result from (bool) cast | Result from empty() | Result from conditional if ({value}) {} |
---|---|---|---|---|
string | “Hello” | true | false | true - inside braces |
integer | >= 1 | true | false | true |
integer | <= -1 | true | false | true |
float | > 0 | true | false | true |
float | < 0 | true | false | true |
string | “” | false | true | false - outside braces |
integer | 0 | false | true | false |
float | 0.0 | false | true | false |
Compound types (no pseudo-types) and their relationship to false
:
Type | Value/Context | Result from (bool) cast | Result from empty() | Result from conditional if ({value}) {} |
---|---|---|---|---|
array | [1, 2, 3] | true | false | true |
callable | function() {} | true | false | true |
object | new stdClass() | true | false | true |
array | [] | false | true | false |
null
and its relationship to false
:
Type | Value/Context | Result from (bool) cast | Result from empty() | Result from conditional if ({value}) {} |
---|---|---|---|---|
NULL | -- | false | true | false |
Outliers (beyond the scope of this RFC):
Type | Value/Context | Result from (bool) cast | Result from empty() | Result from conditional if ({value}) {} |
---|---|---|---|---|
string | “0” | false | true | false |
Row 1: Even though the string contains a character, it is considered false
and empty
as it appears the value is passed through intval()
, which results in zero and is automatically inerpeted as false
and empty
as of PHP 8.
No known incompatibles.
PHP 8.1 or later
No known negative impacts.
callable
? Would a class using __invoke()
default to true? Can the same class implement Falsifiable and return false?bool|Falsifiable
implementation and need.__construct()
__toString()
and Stringable
. RFC for Stringable
listed concerns related to __toString()
already being a method. Would look at the implementation as it should be similar, level of knowledge to implement is not there yet.null
behavior remains unchanged.bool
including one named toBool()
.
Leaves open, and does not directly pursue, the possibility of a future emptiness check or return. If passed to empty()
a false instance would be considered empty; however, there may come a future in PHP wherein developers could specify the “emptiness” of an instance.
yes/no
Links to any external patches and tests go here.
If there is no patch, make it clear who will create a patch, or whether a volunteer to help with implementation is needed.
Make it clear if the patch is intended to be the final patch, or is just a prototype.
For changes affecting the core language, you should also provide a patch for the language specification.
Temporary PR for implementation, with status notes: https://github.com/joshbruce/php-src/pull/2
After the project is implemented, this section should contain
These RFCs are identified as similar in spirit to this [concept], possibly helped by this [concept], or this [concept] is potentially helped by the result of the linked RFC.
Originally posted on GitHub, edit history available there: 0.2.0 and 0.1.0
Implemented:
null
null
or one (or more) types from function or methodnull
Accepted:
Under review and discussion:
Declined:
__toBool()
by proxy via operators__invoke()
Other:
Keep this updated with features that were discussed on the mail lists.