I would like to propose a clean way to add some strong typing to PHP in a
manner that is almost fully backward compatible (there is a behavior change
with PHP 7 type declarations). As I don't have access to the add RFC's to
the wiki I'll place this here.

Before I begin detailing this I want to emphasize this syntax is optional
and lives alongside PHP's default scalar variables. If variables aren't
declared using the syntax detailed below than nothing changes.  This is not
only for backwards compatibility, but it's also to keep the language easy
to learn as understanding datatypes can be a stumbling block (I know it was
for me at least).

VARIABLE DECLARATION

Currently the var keyword is used to formally declare a variable.  The
keyword will now allow a type argument before the var name as so

var [type] $varname;

If the type is omitted, scalar is assumed.  If Fleshgrinder's scalar RFC is
accepted then it would make sense to allow programmers to explicitly
declare the variable as a scalar, but in any event when the type is omitted
scalar must be assumed for backwards compatibility.

The variables created by this pattern auto cast anything assigned to them
without pitching an error. So...

var string $a = 5.3;

The float of 5.3 will be cast as a string.

For some this doesn't go far enough - they'd rather have a TypeError thrown
when the assignment isn't going to work.  For them there is this syntax

string $a = "Hello";

Note that the var keyword isn't used.


FUNCTION DECLARATION

PHP 7 introduced type declarations.  This RFC calls for these to become
binding for consistency, which introduces the only backward compatibility
break of the proposal.  Consider the following code.

function foo ( string $a ) {
  $a = 5;
  echo is_int($a) ? 'Yes' : 'No';
}

Under this RFC "No" is returned because 5 is cast to a string when assigned
to $a. Currently "Yes" would be returned since a scalar has the type that
makes sense for the last assignment.

I believe this is an acceptable break for two reasons. 1, the type
declaration syntax is relatively new.  2, changing the type of a variable
mid-function is a bad pattern anyway.


OBJECT TYPE LOCKING

Currently there is no way to prevent a variable from being changed from an
object to something else. Example.

$a = new SomeClass();
$a = 5;

If objects are allowed to follow the same pattern outlined above though
this problem is mostly solved..

SomeClass $a = new SomeClass();
var SomeClass $a = new SomeClass();

QUESTION: How do we handle the second auto casting case? $a is not allowed
to not be a SomeClass() object, but there are no casting rules. We have
three options:
1. Throw an error on illegal assign.
2. Allow a magic __cast function that will cast any assignment to the
object.
3. Create a PHP Internal interface the object can implement that will
accomplish what 2 does without the magic approach.

Note that 1 will need to occur without implementation. 2 and 3 are not
mutually exclusive though my understanding is PHP is moving away from magic
functions.


CLASS DECLARATION
Again, by default class members are scalars. The syntax translates over
here as might be expected.

class SomeClass {
  public var string $a;
  protected int $b;
  private SomeOtherClass $c;
  public var SomeThirdClass $d;
}

Note a default value doesn't need to be provided.  In the case of object
members, these types are only checked for on assignment to prevent
recursion sending the autoloader into an infinite loop.

Also note that one of the functions of setters - guaranteeing correct type
assignment - comes free of charge with this change.


COMPARISON BEHAVIOR
When a strongly typed variable (autocasting or not) is compared to a scalar
variable only the scalar switches types. The strict comparison operator is
allowed though it only blocks the movement of the scalar.

Comparisons between strongly typed variables are always strict and a
TypeError results if their types don't match. This actually provides a way
to force the greater than, lesser than, and spaceship operation to be
strict.


FUNCTION CALLING
When a strong typed variable is passed to a function that declares a
variable's type then autocasting will occur so long as the pass is not by
reference.  For obvious reasons a TypeError will occur on a by reference
assignment..

function bar( string $a) {}
function foo( string &$a ) {}

$a = 5.3;
foo( $a ); // Works, $a is a scalar, so it type adjusts.
var bool $b = false;
foo( $b ); // TypeError, $b is boolean, function expects to receive a
string by reference.
bar($b); // Works since the pass isn't by reference, so the type can be
adjusted for the local scope.


CONCLUSION
I believe that covers all the bases needed. This will give those who want
things to use strong typing better tools, and those who don't can be free
to ignore them.

Reply via email to