Live data from Hacker News

Making PHP Safer

tech.blog.box.com

51–53 of 53 posts

Re: Making PHP Safer

#51
post #33

Type hinting for scalar types has been on and off the TODO list for PHP for a while now. PHP already has type hinting for arrays and objects, but not for scalar types like int, float, and string. It's been on the roadmap forever but nobody seems to know when the feature will actually land in a stable release. That's because type hinting for built-in scalar types is more difficult than it sounds in a dynamic language…

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.

Re: Making PHP Safer

#52
post #4

I'd really like to know why they chose to do this instead of trying to make spltypes and type hinting more robust. Annotations seem like an ugly way of doing this.

One of the authors here. We didnt want to change the AST in a way that would be backwards incompatible with vanilla PHP. There is some luxury in being able to turn off this extension when needed. We also have a large enough existing codebase that switching to SPL types everywhere was a non-starter. Our goal here was partly to be able to use PHP's native types and still get some good coverage. Phpdoc may not seem idea…

Thank you for your response, that's good reasoning and I definitely see it works well for your use case. It just seems as if multiple companies are coming up with solutions to work around deficiencies in PHP which is causing fragmentation in the language. That's not going to stop me from using this extension in a new project I'm starting though. Are there any areas of this extension you'd like to see improved since I find the best way for me to understand an extension internally is to work on it.

Re: Making PHP Safer

#53
post #33

Type hinting for scalar types has been on and off the TODO list for PHP for a while now. PHP already has type hinting for arrays and objects, but not for scalar types like int, float, and string. It's been on the roadmap forever but nobody seems to know when the feature will actually land in a stable release. That's because type hinting for built-in scalar types is more difficult than it sounds in a dynamic language…

Java has trouble with this as well:

``` Integer foo = 5; int bar = 5; System.out.println(foo == bar); //true ```

Post reply on HN