Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recognise exceptions as instances of Throwable #91

Closed
hkdobrev opened this issue Jan 18, 2017 · 13 comments
Closed

Recognise exceptions as instances of Throwable #91

hkdobrev opened this issue Jan 18, 2017 · 13 comments

Comments

@hkdobrev
Copy link
Contributor

With --level 5, I get errors such as this one:

Parameter #1 $throwable of static method Foo::processThrowable() expects Throwable, FooException given.

Of course in PHP 7, every exception is an instance of the special Throwable interface.

It would be nice if PHPStan knows about it out of the box.

@ondrejmirtes
Copy link
Member

ondrejmirtes commented Jan 18, 2017 via email

@ondrejmirtes
Copy link
Member

This is what I suspect your situation is:

https://3v4l.org/EmbS4

This is what you should do :)

https://3v4l.org/LmPgi

@hkdobrev
Copy link
Contributor Author

Indeed this is the case, the exception I'm getting is an interface.

However, it is not from my code, but from a 3rd-party library - https://github.com/thephpleague/omnipay-common/blob/b1440bd7c/src/Omnipay/Common/Exception/OmnipayException.php

So I could write a new interface extending both theirs and Throwable, and then using that in my code, but should I really have to do that for every 3rd-party exception interface?

@ondrejmirtes
Copy link
Member

ondrejmirtes commented Jan 18, 2017 via email

@VasekPurchart
Copy link
Contributor

I get the motivation to automatically add the Throwable type, but I think, this is a double edged sword, because that also means, that if something is in fact not an Throwable/Exception it may very well be a bug and the worst thing about this is that PHP will not throw an error about this. On the other hand in throws, this is checked by PHP: https://3v4l.org/OBHAW.

This is exactly the kind of bugs I would expect PHPStan to notify my about - the throw case you will discover when you run the code (which is not ideal but at least something), but the catch case can only be discovered by writing test for the catch excecution path you expect to happen. Here is a code sample to better illustrate what I mean https://3v4l.org/RHqI2:

<?php

class Foo
{
    
}

class FooException extends \Exception
{
    
}

try {
    throw new \FooException();
} catch (\FooExceptio $e) {
    throw new \Exception('Never called because class does not exist');
} catch (\Foo $e) {
    throw new \Exception('Never called beacause class does not match exception and never will (because it is not an exception/throwable subtype)');
}

So either the class can:

  1. not exist - which is probably already checked by PHPStan (please correct me if am wrong)
  2. be an existing class, which is not subtype of exception/throwable - which is checked now and this issue is about this

So I think it is still usable to check it, also level 5 should be very strict as it is, so at first I wanted to propose pushing this to a higher level, but I think it might actually be fine. So I believe this is on par with the other cases, where the type information is missing and can either be supplied in other way, or ignored.

@ondrejmirtes
Copy link
Member

@VasekPurchart The class referenced inside the catch parentheses is checked whether is an interface or whether is a class implementing \Throwable: https://github.com/phpstan/phpstan/blob/master/src/Rules/Exceptions/CatchedExceptionExistenceRule.php#L51 So I think this is OK since any interface can be implemented by a class that extends \Exception.

This issue is about what happens inside the catch block with $e next - if I want to assign it to a property or send as an argument to a method, it should pass the implements \Throwable check because it was already caught and we can be sure about it.

To prevent the issue you're talking about, I'd have to check if some class that implements the caught interface actually extends \Exception but I think this issue is so minor it's not worth the implementation and performance cost (of discovering and creating reflections for all 1st and 3rd party classes used in the project).

@hkdobrev
Copy link
Contributor Author

@ondrejmirtes Thank you for your efforts! I just came here to say that I'm using a method call on the exception where a Throwable is expected, but you've posted first:

class Logger
{
    /**
     * @param Throwable $throwable
     */
    public function logException($throwable) // I don't have a typehint here so it works on 5.6 as well
    {
        // log exception with trace here
    }
}

try {
    // some code here
} catch(OmnipayException $exception) {
    Logger::logException($exception);
}

I hope this context helps you and others reading the issue.

@ondrejmirtes
Copy link
Member

ondrejmirtes commented Jan 19, 2017

But one check is truly missing – whether the type in throw expression is an object that implements \Throwable :) Will add that in the next version.

@dkarlovi
Copy link

dkarlovi commented Aug 1, 2017

Awesome to have an issue, come to GH to open it and find it's already there, fully explained and everything. 👍

@thePanz
Copy link
Contributor

thePanz commented Aug 9, 2017

Any update on this @ondrejmirtes ?
It would fix some warnigs we're getting from a GuzzleException that we're catching: it is an interface (https://github.com/guzzle/guzzle/blob/master/src/Exception/GuzzleException.php) and a further use of $e->getMessage() is not recognized by PhpStan

@ondrejmirtes
Copy link
Member

ondrejmirtes commented Aug 9, 2017 via email

@ondrejmirtes
Copy link
Member

Fixed mainly with intersection types #410 and this 480e9c8.

If you like PHPStan and use it to find bugs in your projects, I'd love if you supported it on Patreon. I have many ideas how to make PHPStan even smarter and faster but doing it over evenings and weekends is not sustainable longterm. I'm trying to make developing open-source a viable livelihood alternative. Even a $1 a month helps, but the more you contribute, the closer I am to my goal to work on it fulltime :)

@thePanz
Copy link
Contributor

thePanz commented Sep 9, 2017

Thanks @ondrejmirtes!
And yes, we're planning to support the project :)

@lock lock bot locked as resolved and limited conversation to collaborators Dec 20, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants