Skip to content

Instantly share code, notes, and snippets.

@marcvdm
Last active August 29, 2015 14:17
Show Gist options
  • Save marcvdm/ac9334e3bc529244a403 to your computer and use it in GitHub Desktop.
Save marcvdm/ac9334e3bc529244a403 to your computer and use it in GitHub Desktop.
Scalar Type Hinting

I have noticed that there is a lot of commotion around the implementation of the scalar type hints. I haven't read all the discussions and i am certainly not a php core expert. I do however use (and love) php for a long time now.

So here is my take on how i think it should work.

Keep the default behavior so that legacy code keeps working and if you want to build a class/function that needs some stricter type hinting, then type hint it.

class Foo {}

function default_behavior($int, $string, Foo $foo = null)
{
	var_dump($int, $string, $foo);
}

default_behavior(1, 'string', new Foo()); // int 1, string 'string', object(Foo)
default_behavior(1.5, 'string', new Foo()); // float 1.5, string 'string', object(Foo)
default_behavior('2 apples', 1.5); // string '12 apples', float 1.5, null

The above shows how you would normally use type hinting. If you want people to have flexibility in what they send as an argument, then leave the type hint. You can then use your own validation if necessary.

And if you want to exactly define what the argument should be, then type hint it.

function strict_behavior(float $float, int $int, string $string, Foo $foo = null)
{
	var_dump($float, $int, $string, $foo);
}

strict_behavior(1, 2, 'string', new Foo()); // float 1, int 2, string 'string', object(Foo)
strict_behavior(1.5, 2, 'string', new Foo()); // float 1.5, int 2, string 'string', object(Foo)
strict_behavior(1, 1.5, 'string'); // Error Exception - Argument 2 passed to strict_behaviour must be of the type integer, float given
strict_behavior('1', '1.5', 2); // Error Exception - Argument 1 passed to strict_behaviour must be of the type float, string given

This would be the same.

function strict_default_behavior($float, $int, $string, Foo $foo = null)
{
	if(is_int($float)) $float = floatval($float);
	if(!is_float($float)) throw new ErrorException("Argument 1 passed to strict_default_behavior must be of the type float, ".gettype($float)." given");
	if(!is_int($int)) throw new ErrorException("Argument 2 passed to strict_default_behavior must be of the type integer, ".gettype($int)." given");
	if(!is_string($string)) throw new ErrorException("Argument 3 passed to strict_default_behavior must be of the type string, ".gettype($string)." given");

	var_dump($float,$int,$string,$foo);
}

strict_default_behavior(1, 2, 'string', new Foo()); // float 1, int 2, string 'string', object(Foo)
strict_default_behavior(1.5, 2, 'string', new Foo()); // float 1.5, int 2, string 'string', object(Foo)
strict_default_behavior(1, 1.5, 'string'); // Error Exception - Argument 2 passed to strict_behaviour must be of the type integer, float given
strict_default_behavior('1', '1.5', 2); // Error Exception - Argument 1 passed to strict_behaviour must be of the type float, string given

Maybe i'm missing parts of the discussion but i like to think that the whole scalar type hinting idea was to extend the already existing type hints like array, callable and classes.

If you use a package from someone else and that person has defined an api for a method that requires you to give an array, callable or a class, then it doesn't work when you give an object when the method requires an array.

This is already happening in the latest version of php.

function already_fails_with_current_version(array $array)
{
	var_dump($array);
}

already_fails_with_current_version((object)['var' => 'value']); // Error Exception - Argument 1 passed to already_fails_with_current_version() must be of the type array, object given

So why are the internals discussing weak and strict and not about the already default behaviour that already exists?

For the int/float part, i think that int should be converted to float but not the otherway around.

class Float {
	public $value;
	public function __construct($value){
		$this->value = floatval($value);
	}
}
class Int extends Float {
	public function __construct($value){
		$this->value = intval($value);
	}
}

function convert(Int $int,Float $float)
{
	// This would then be in the core of php
	if($float instanceof Int){
		$float = new Float($float->value); 
	}

	var_dump($int,$float);
}

convert(new Int(1),new Float(1.5)); // object(Int) public 'value' => int 1, object(Float) public 'value' => float 1.5
convert(new Int(1),new Int(2)); // object(Int) public 'value' => int 1, object(Float) public 'value' => float 2
convert(new Float(1.5),new Float(2.5)); // Argument 1 passed to convert() must be an instance of Int, instance of Float given
convert(new Float(1.5),new Int(2)); // Argument 1 passed to convert() must be an instance of Int, instance of Float given

Hope this makes sense. I just wanted to write down what i think about it and i hope that this or something like it will be in the next release soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment