I'm back to doing some PHP beta testing. Or more "exploration" than testing, as I'm not being very rigorous about it. PHP 7 is tup to beta 2 now (download for Windows here: "PHP 7.0 (7.0.0beta2)"). Beta 1 came and went so quickly I never even installed it, let alone looked at it. I've had too ColdFusion 12 testing to do, and messing around with JavaScript. Even at work a the moment I'm pretty much doign JavaScript day-in/day-out at present. I've probably written 100 lines of PHP code (mostly unit tests) in the last few weeks.
But, anyway, the beta waits for no person, so I figured I should look at some more stuff.
PHP 7 has expanded its type-chekcing capabilities on function arguments. In "hilarious" very typical PHP language "design" fashion, PHP has had argument type-checking on object types for some time (I cannot be bothered looking up since when), but it's never had type checking on scalar values until now. I dunno what the thinking was there. I'm sure there's an oooh so good excuse for it somewhere, but I'm equally sure that would simply start draining IQ points if a person was to read it. I shall avoid as I've already got booze for draining my IQ.
Anyway, the good side of the situation is that they've added a bit of uniformity to the language here. Scalar arguments can be type-checked now too. Kinda. Let's look at that first, as it's an easy one.
<?php
// scalarArg.php
require __DIR__ . "/safeRun.php";
safeRun("Passing an int", function(){
$i = 1;
$result = takesInt($i);
echo "Returned:<br>";
var_dump($result);
});
safeRun("Passing a float", function(){
$f = 1.1;
$result = takesInt($f);
echo "Returned:<br>";
var_dump($result);
});
safeRun("Passing a boolean", function(){
$b = true;
$result = takesInt($b);
echo "Returned:<br>";
var_dump($result);
});
safeRun("Passing a string", function(){
$s = "1";
$result = takesInt($s);
echo "Returned:<br>";
var_dump($result);
});
safeRun("Passing a string which in no way can be considered an int", function(){
$s = "not an integer";
$result = takesInt($s);
echo "Returned:<br>";
var_dump($result);
});
safeRun("Passing a PHP object", function(){
$d = new DateTime();
$result = takesInt($d);
echo "Returned:<br>";
var_dump($result);
});
class C {}
safeRun("Passing a bespoke object", function(){
$o = new C();
$result = takesInt($o);
echo "Returned:<br>";
var_dump($result);
});
On PHP 5 this results in:
Passing an int
Catchable fatal error: Argument 1 passed to takesInt() must be an instance of int, integer given, called in D:\src\php\php.local\www\experiment\7\types\scalarArg.php on line 12 and defined in D:\src\php\php.local\www\experiment\7\types\scalarArg.php on line 6
(it claims it's a catchable fatal error, but I'm buggered if I know how to catch an error in PHP 5. One can in PHP 7, so let's ignore this).
This demonstrates this shortfall in PHP 5. It doesn't allow scalar type checking (where scalars are simple values like integers, strings, etc), so PHP is assuming my return type of int is actually some sort of object. Which the argument is not, so I get a fatal error. Nice. At most this should be a type-check exception, but still. PHP sucks at error handling, we know this.
Here's how PHP 5 is expecting an "int" return type to "work":
// intObject.php
require __DIR__ . "/safeRun.php";
class int {}
function takesInt(int $i){
return $i;
}
safeRun("Passing an int (object)", function(){
$i = new int();
$result = takesInt($i);
echo "Returned:<br>";
var_dump($result);
});
This yields:
Passing an int (object)
Returned:
object(int)#2 (0) { } Ran OK
And that's correct (for PHP 5).
Anyway, PHP 7:
Passing an int
Returned:
int(1)
Ran OK
Passing a float
Returned:
int(1)
Ran OK
Passing a boolean
Returned:
int(1)
Ran OK
Passing a string
Returned:
int(1)
Ran OK
Passing a string which in no way can be considered an int
Code: 0
Message: Argument 1 passed to takesInt() must be of the type integer, string given, called in D:\src\php\php.local\www\experiment\7\types\scalarArg.php on line 40
Passing a PHP object
Code: 0
Message: Argument 1 passed to takesInt() must be of the type integer, object given, called in D:\src\php\php.local\www\experiment\7\types\scalarArg.php on line 47
Passing a bespoke object
Code: 0
Message: Argument 1 passed to takesInt() must be of the type integer, object given, called in D:\src\php\php.local\www\experiment\7\types\scalarArg.php on line 55
So it seems we're doing a bit of duck-typing here. And I don't think it's doing it right. Let's look at the results.
- passing an int just works. Phew.
- passing a float "works",
- but loses precision. No. I can understand duck-typing a float which has no decimal part: that'd be OK. But
- passing a boolean works
- a string which can be duck-typed as an int will work. I guess this is OK.
- a string which cannot be duck-typed as an int raises an exception or error of some type. Oddly the Throwable interface does not seem to expose a method to get the actual type out?!
- and passing objects likewise error.
<?php
// classWithToString.php
require __DIR__ . "/safeRun.php";
class Person{
protected $name;
function __construct($name){
$this->name = $name;
}
}
class StringablePerson extends Person {
function __toString(){
return $this->name;
}
}
safeRun("Outputing a StringablePerson", function(){
$son = new StringablePerson("Zachary");
echo "The boy's name is $son<br>";
});
safeRun("Outputing a Person (which gives a fatal error)", function(){
$son = new Person("Zachary");
echo "The boy's name is $son<br>";
});
Output:
Outputing a StringablePerson
The boy's name is Zachary
Ran OK
Outputing a Person (which gives a fatal error)
Catchable fatal error: Object of class Person could not be converted to string in D:\src\php\php.local\www\experiment\7\types\classWithToString.php on line 30
The error demonstrates the success of the first test here. My test function expects a string, and an object of a class implementing
__toString()
passes muster. This is excellent.The next thing I wondered about is arrays. I did not know off the top of my head whether PHP already supported specifying an array as as an argument type, so here's a baseline:
<?php
// arrayArg.php
require __DIR__ . "/safeRun.php";
function countArray(array $array){
return count($array);
}
safeRun("Passing an array as an argument", function(){
$rainbow = ["whero", "karaka", "kowhai", "kakariki", "kikorangi", "poropango", "papura"];
$result = countArray($rainbow);
echo "The rainbow has $result colours<br>";
});
safeRun("Passing a string as an argument", function(){
countArray("Not an array");
});
And running this on PHP 5 demonstrates "array" as a type is already supported:
Passing an array as an argument
The rainbow has 7 colours
Ran OK
Passing a string as an argument
Catchable fatal error: Argument 1 passed to countArray() must be of the type array, string given, called in D:\src\php\php.local\www\experiment\7\types\arrayArg.php on line 19 and defined in D:\src\php\php.local\www\experiment\7\types\arrayArg.php on line 7
OK, but let's push the boat out a bit. It's a very simplistic requirement that an argument needs to be an array. The difference between an array and an individual value is one of plurality, not of type. If a function might take a string as an argument, the equivalent for an array situation would be that a function might take any array of strings. Not simply "an array".
Here's an example:
<?php
// arrayOfStringsArg.php
function reverseArrayElements(string[] $strings){
return array_map(function($string){
return strrev($string);
}, $strings);
}
$wobniar = ["orehw","akarak" ,"iahwok", "ikirakak","ignarokik","ognaporop", "arupap"];
$rainbow = reverseArrayElements($wobniar);
var_dump($rainbow);
This demonstrates it's not simply an array we need here, it's an array of a specific type of object (points off it you write in saying "strings aren't objects in PHP": you're missing the point).
Other than when writing array utility functions (and bloody hell... PHP does not need more of those!), then in general it's the type of element in the array one is concerned about, not that the passed-in argument is simply an array of any old sh!t.
Anyway, it seems PHP has missed a trick here, as this code doesn't even parse:
Parse error: syntax error, unexpected '[', expecting variable (T_VARIABLE) in D:\src\php\php.local\www\experiment\7\types\arrayOfStringsArg.php on line 4
Suck. I checked to see if it was just an oversight with scalars (although give it didn't parse, it didn't look like it), and that failed too (arrayOfObjectArg.php).
I suppose the syntax might be different than I'm guessing, but I can't find what it is if it does exist.
There's some other stuff to test, but I want to write an article on type-checking function return-types (which is completely new to PHP 7, believe it or not?!), and I'll use those examples there. That'll probably be tomorrow, as I've got most of the code written already, I just need to write it up.
--
Adam