2018-01-04 3:37 GMT+01:00 Michael Morris <tendo...@gmail.com>: > Second Draft based on the feedback upstream. > > Target version: PHP 8. > > This is a proposal to strengthen the dynamic type checking of PHP during > development. > > Note - this is not a proposal to change PHP to a statically typed language > or to remove PHP's current loose typing rules. PHP is a weakly typed > language for a reason, and will remain so subsequent to this RFC. This RFC > is concerned with providing tools to make controlling variable types > stronger when the programmer deems this necessary. > > > > VARIABLE DECLARATION > > PHP currently has no keyword to initialize a variable - it is simply > created when it is first referenced. The engine infers the appropriate type > for the variable, and this may be later cast to other types depending on > the context of the code. Objects can have magic functions to carry out this > casting such as __toString. > > It is sometimes useful to explicitly state a variable's type. One case is > when the engine might incorrectly infer the type. For example "073117" is a > valid octal integer but also a date string in mmddyy format, so a > comparison with another date string in the same format could be... amusing. > While there is a string comparison function, that functions presence is > borne of the fact that we can't reliably compare "073117" with say, > "010216" because of the int casting possibility. > > Since the scalar types have already been reserved as keywords they can be > used to declare variables in a manner not unlike C or Java. > > int $a = 073117; > > The var keyword is still around from PHP 4 days but is going unused. In > JavaScript var is used to formally declare a variable though it isn't > required (It remains important because without it JavaScript will search > the scope chain of the current closure all the way to the top scope. If it > doesn't find the reference it only then creates one. This can lead to huge > headaches so the creation of variables without using var is strongly > discouraged in JavaScript). > > Since the keyword is available, let's make use of it. > > var $a = "123"; > > What I propose this will do is formally declare $a, infer it's type, then > LOCK the type from casting. If further assignments are made to the variable > the quantity being assigned will be cast to type desired if possible, > otherwise a type error will be raised. > > var string $a = $_POST['date']; > > This syntax allows the programmer to choose the type rather than allowing > the engine to infer it. Here $_POST['date'] might be provided in date > string that might be confused for an octal int. > > This magical casting is suggested because it follows the spirit of PHP, but > it may not be strict enough. For those the type can be explicitly declared > without using the var keyword as follows. > > int $a = 4; > > In this event a type error will occur on any attempt to assign a value to > $a that isn't an int. > > The variable can still be re-declared in both cases so. > > var $a = 4; > string $a = "Hello"; > > The var keyword can be combined with the new keyword to lock an object > variable so it doesn't accidentally change > > var $a = new SomeClass(); > > As noted above a deliberate redeclare can still change the type of $a. > > If $a is declared with an int type shouldn't it be enought to simply freeze it's type to int? var keyword was used in PHP4 and PHP5 and I suppose no one uses it in PHP7 anymore, why not deprecate it? IMO it shoudl be burned&burried.
If all variable declarations with type would lock it's type then var keyword would be useless am I right? > > > ARRAYS > All members of an array can be cast to one type using this syntax > > var string array $a = [ 'Mary', 'had', 'a', 'little', 'lamb' ]; > int array $b = [1,2,3,5]; > > Personally I really don't like proposed syntax, there are some work in progress in subject of generics and IMO that should be the right way to declare generic types. > Or members can be individually cast > > var $a = [ var 'Todd', var 'Alex' ]; > $b = [string 'id' => int 1, 'name' => string 'Chad']; > > Again, following rules similar to the above. The main reason for doing > this is to insure smooth interaction with the pack and splat operators. > > function foo (var string array $a = ...); > > Here again why not just lock it's type here if we expect $a to be int. I assume if someone declares it as array or string he did it with some purpose. > And speaking of functions, that's the next section. > > > > FUNCTION DECLARATION > > Variables are also declared as arguments to functions. I propose using the > var keyword to lock the resulting variable and perform a cast if possible. > > function foo( var string $a, var $b ) {} > > Note that using var without explicitly calling type will be legal if rarely > used for consistency reasons. Also, someone might have a use for an > argument who's type could be anything, but won't change after it is > received. > > The type can also be inferred from the default. > > function foo( var $a = "hello" ) {} > > This syntax is essentially doing a redeclare of the variable. This could be > very troublesome with references, so a Type error will result if this is > tried. > > function foo ( var &$a = "Hello" ) {} > > $b = 3; > foo($b); > > With objects the var keyword can be used to prevent the function from > changing the object. > > function foo ( var SomeClass $a ) {} > > > > > > CLASS MEMBER DECLARATION > > Variables also appear as object members. Following the pattern established > above their types can be locked. A couple note though > > class SomeClass { > > var $a = '3'; > public var $b = 'hello'; > > } > This is awkward, seems like returning to PHP4 again. public is strictly pointing out that $a member is public and I suppose everyone got used to it. > > For backwards compatibility the var keyword by itself must be equivalent to > "public". It is only when a scope operator is present that var takes on its > new meaning in this context. > > Magic __set and __get cannot access variables with locked types because, > well, it will be a bloody mess. Basic getter/setter behavior (insuring the > datatype is correct) is accomplished just with the ability to type lock. > Beyond that explicit getters and setters will be needed, and once again an > inbuilt interface will be invoked. The interface is a little magical though > like the ArrayAccess interface. > > class SomeClass implements AccessorInterface { > > protected $a = ''; > protected var $b = "string"; > protected int $c = 5; > > public get_a () { return $this->a; } > public set_b( $val ) { $this->b = (string) $val; } > public get_c():int { return $this->c; } > > } > IMO there are better proposals for getters/setters in PHP RFC's. You rely here on specific function naming which may collide or mess with popular naming conventions. > > Unlike userland interfaces, the AccessorInterface gets its potential method > names from the properties of the members as well as their signatures. > These methods follow the get_[varname] or set_[varname]. Getters must > return the same type as the underlying var if specified. Setters don't have > to take a matching argument as often their job is conversion. > > > CASTING INTERFACES > > As these elements are introduced the ability of objects to control how they > are cast into scalars needs better improving. I propose interfaces in the > vein of ArrayAccess with the pattern below. > > interface IntegerCastable { > public function CastToInteger():int; > } > > PHP will call the function for the appropriate casting operation. Also, if > the object with at least one of these interfaces is echo'ed out then that > cast will be performed, in the priority order string, float, int, bool. > > Note - The magic __toString and the StringCastable interface are mutually > exclusive - trying to create an object with both will trip a parseError. > > > > > COMPARISON BEHAVIOR > Controlling variable types gives us more granular control over comparisons > but determining which of the variables in a comparison can be coerced. When > a variable with a locked type is compared to another variable only that > other variable can be coerced. > > Even better, 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, etc. to be strict. > > > > > PERFORMANCE IMPLICATIONS - TURNING IT OFF. > I imagine implementing all of the above will incur a performance hit. Yet, > pretty much all of this could be done with strategic use of assert(). PHP > is happy to not do this checking - much of it is for program testing and > peace of mind. So the last piece of the proposal is to insure all of the > above can be disabled. > > I'm personally in favor if turning it off using the existing zend.assertion > flag since all these checks are part of Design by Contract anyway. In > addition to turning off all of the above I recommend allowing other > function type declarations to be turned off by zend.assertion. > > There are reasons not to do that and use a separate flag for one or both of > these methods. While I can live with that I would like to point out that > debug flag proliferation can lead to confusion. > > The crux of my argument for using the zend.assertion flag is that these > type checks are all, at the end of day, engine level assertions. PHP ships > with zend.assertion set to 1, and with PHP 8 we can keep that default and > recommend to providers to not assume it's safe to set it to -1 since there > is a small, but not insignificant, chance that old code relying on Type > declarations to be on might corrupt user data. I admit this would be > painful in the short term, but it is better for the long term health of the > language and parser. > > > > > 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. > Please register a wiki account and put proposed RFC to the RFC's list. -- regards / pozdrawiam, -- Michał Brzuchalski about.me/brzuchal brzuchalski.com