Skip to content

Add ability to type cast objects. #107

@Fuco1

Description

@Fuco1

Hi.

In languages like Java you can cast objects at runtime with the "usual" cast syntax ClassA a = (ClassA) new ClassB(); This syntax is not recognized in php, but sometimes runtime casts are necessary when the system can't properly figure out the types.

One instance where this is extra painful is the (arguably bad) service locator pattern. Often you have some "module" hierarchy where every manager/worker class extends some interface or abstract class and the best you can do with the "getService" method on the service locator is to return that interface... but it is never the correct type!

Example code:

class Service { }

class A extends Service { }

class B extends Service { }

class Code {

    public function main() {
        $service = $this->getService('A'); // returns 'Service'
        $this->test($service); // call fails to check even if $service is actually 'A'
    }

    /**
     * @return Service superclass
     */
    public function getService($type) { return new $type(); }

    public function test(A $a) { }
}

I propose an extension to syntax like this

$super = /*(A)*/ $this->getService('A');

The "commented cast" will alter the type of the following expression, in this case the method call.

This is unsafe (the sevice returned might not be A and it is impossible to know), so in case of e.g. Java a warning is generated by the compiler which has to be suppressed manually. Here it is not required because this is a deliberate annotation and so the programmer probably knows why they put it in.

This is very helpful on older projects littered with explicit calls to service locators or containers (I happen to work on such a codebase) and would allow tons of type checking which is simply impossible now.

I've tried to hack a bit on Scope.php's method getType to add the support and it doesn't seem that difficult (basically check any Expr to see if it has a comment attached with this particular syntax and then return the type directly), but I'm failing to figure out how to do namespace/uses resolution so that I don't have to always provide the full type.

Here is the code I've put in and it works, but as you can see I check for MethodCall explicitly (which is only one of many Exprs). This is because php-parser attaches the comment to all subnodes and not just the parent node, which in this case would for example also cast $this to A and then complain A has no such method as test). There is an issue opened for that: nikic/PHP-Parser#253.

if ($node instanceof Expr\MethodCall && isset($node->getAttributes()['comments'])) {
    $name = $node->getAttributes()['comments'][0]->getText();
    if (preg_match('#/\*\(.*?\)\*/#', $name)) {
        $type = substr($name, 3, -3);
        // this is not correct, I guess there is some "resolver" from string to type
        return new ObjectType($type, false);
    }
}

I will happily hack on this if someone can provide guidance on how to resolve the types/namespaces/renames.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions