A good starting point would be to implement type-hinting for a generic 'scalar', i.e.; mymethod(scalar $foo)
This simple type-hint would already solve a lot of problems where non-scalar values cause havoc (least of all,
automatically casting of arrays to 'Array' as string and other cases, like trying to use an array as key in an array).I've seen a proposal to use a generic 'numeric' type-hint. I'm not sure this is a very good idea, really. If it handles values
in the same way as PHP's is_numeric(), this will lead to confusing situations; "0xA", after all is a numeric value. To allow values
like this, it's better to specify 'scalar' as type-hint and handle conversion/casting manually. I personally don't like the fuzzy '~int' (int-'ish') type-hint, it simply doesn't look very clean (looks like some kind of a RegEx).
Besides, how should a method pick the best way to cast a non-integer value? (Again, is "0xA" a typo by the user, or does it represent a hex?). The only situation where automatic casting/fuzzy matching may be suitable, is for Object that implement a specific interface (e.g. ArrayAccess interface). By lack of real polymorphism in PHP, I'd opt for a more explicit approach, while keeyping flexibility by allowing multiple types to be specified,
similar to the PhpDoc notation. Something like; 1. The 'classic' approach // Accepts any type
myClassicMethod($foo)
2. Explicitly 'classic' approach // Explicitly accepts any type
myClassicMethod(mixed $foo)
// Effectively, this results in the same behavior as 1.,
// however, it clearly shows that 'mixed' values are accepted
// by-design and not because of omitance
3. Strict-ish approach // Only accepts scalar values
myClassicMethod(scalar $foo)
4. The 'polymorphic' approach // Accepts 'int' or 'string', but NOT 'bool', 'float' an non-scalar types
myFuzzyMethod(int|string $foo)
{
// Further type-casting and checking
}
5. The 'strict' approach // Only accept 'int' values, NO 'int-ish' values
myStrictMethod(int $foo)
// The code *calling* the method is responsible for converting
// arguments to the right type, using any method nescessary
// for the situation
echo myStrictMethod(intval($foo));
// or, maybe a simple cast is sufficient;
echo myStrictMethod((int)$foo);
In all approaches above, handling invalid arguments (out-of-range etc.) should be the responsibility of the method.Having said that, unless the method's actual purpose is to convert/cast types (e.g. convertToBool(int|string $value)),
the last approach (strict) offers the clearest separation of responsibility (again, IMO); In many cases, a method cannot be held responsible for picking the best approach to convert values, simply because
it does not know where an argument originates, what it should tollerate and how conversion should be handled. Take for example, a web-form containing a 'currency' input. To offer a user-friendly experience, in many cases it
is preferable to be 'forgiving' when handling 'faulty', but (sometimes) trivial user input, like; - User includes currency-symbol in the field (€ 123,45) - User uses incorrect decimal-separator for the website's locale (123.45 in stead of 123,45) Conversions like this are part of the 'business logic' of the application and cannot be reliably performed automatically
by the type-hinted method without causing unexpected results. Type hinting should just do that; 'hint' what is expected and 'throw' if this expectation is not met. I would really oppose to too many 'fuzzy' matches. Automatic conversion between types (the infamous PHP type-jugling) is
a major reason for many headaches while developing with PHP. Disguising them as 'type-hints' if only fooling ourselves. I would like an option to define custom type-casting handlers to have more control over the way PHP will 'juggle' values
and to handle specific situations. |