rfc:objects-can-be-falsifiable

This is an old revision of the document!


PHP RFC: Objects can be declared falsifiable

  • Version: 0.9
  • Date: 2020-07-14
  • Author: Josh Bruce, josh@joshbruce.dev
  • Implementer: seeking (or someone willing to answer specific questions when I have them)
  • Status: In discussion

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 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:

  1. provide a way for an instantiated object to be interpreted as false, without null while being consistent with similar approaches as of PHP 8,
  2. provide a new 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
  3. no BC breaks (no polyfill is planned at this time).

Introduction

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:

  1. PHP language guidance reserves methods beginning with two underscores for language features. (Including __construct() replacing old style constructors in PHP 7.)
  2. The interface would be in the root PHP namespace; therefore, should not collide with other implementations and, if so, can be modified with as.
  3. The implementation does not replace, or interfere with the interactions of null types; therefore, nullable types and the possibility of nullsafe operators can work as expected.

Proposal

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()
// ];

Truthiness tables

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.

Backward Incompatible Changes

No known incompatibles.

Proposed PHP Version(s)

PHP 8.1 or later

RFC Impact

No known negative impacts.

Open Issues

  • As of this writing I do not have the knowledge, practice, and practical understanding of implementing within PHP internals to implement this myself. If you're interested in (helping) implement this concept, please do reach out (help may be in the form guidance and instruction or full implementation, up to you).
  • Would presume impact similar to __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.

Unaffected PHP Functionality

  • null behavior remains unchanged

Future Scope

None.

Proposed Voting Choices

yes/no

Patches and Tests

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.

Implementation

After the project is implemented, this section should contain

  • the version(s) it was merged into
  • a link to the git commit(s)
  • a link to the PHP manual entry for the feature
  • a link to the language specification section (if any)

References

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.

Implemented:

Accepted:

Under review and discussion:

Declined:

Rejected Features

Keep this updated with features that were discussed on the mail lists.

rfc/objects-can-be-falsifiable.1594770209.txt.gz · Last modified: 2020/07/14 23:43 by joshbruce