[VOTE] Catching Multiple Exception Types

php.internals

Bronisław Białek

10 years ago
Hi Internals, We've opened the vote on https://wiki.php.net/rfc/multiple-catch Voting will end on 2015-05-01
-- Regards, Bronisław Białek.

Björn Larsson

10 years ago
Den 2016-04-17 kl. 18:51, skrev Bronisław Białek:
> Hi Internals, > > We've opened the vote on https://wiki.php.net/rfc/multiple-catch > Voting will end on 2015-05-01 >
Very nice that you added "When is it usefull". Could also have added when is it not useful, but now we are in voting... This feature reminds me about Group Use declarations, adding syntax sugar to improve code quality and make code updates less error prone. Regards //Björn Larsson PS Typo in "usefull", think it's only one l.

Dan Ackroyd

10 years ago
I voted yes. It's really not that common that I have to do stuff in exception handling, when I do need to do some clearing up, or checking to see if the operation that threw the exception needs to be re-tried, having to repeat that code in all of the catch statements that need it, is just nasty duplication. Although it's a small change, having this is a very nice small improvement that doesn't break anyone's existing code. Thanks for putting it forward. cheers Dan On 17 April 2016 at 17:51, Bronisław Białek <after89@gmail.com> wrote:

Marco Pivetta

10 years ago
Heya, I voted "NO" due to previous discussion. TL;DR: this is FAR off the 80/20 use-case for a language syntax change. C&P from my answer elsewhere: The following typical example is something REALLY rare, and requiring a parser change for it seems excessive: try { // ... } catch (InvalidArgumentException $e) { // same handling } catch (PDOException $e) { // same handling } catch (BadMethodCallException $e) { // same handling } These 3 exceptions usually result in separate handling anyway. If same handling is needed, you can as usual extract a private method (if you are in a class) and deal with it there: try { // ... } catch (InvalidArgumentException $e) { $this->sameHandling($e); } catch (PDOException $e) { $this->sameHandling($e); } catch (BadMethodCallException $e) { $this->sameHandling($e); } private function sameHandling(Throwable $caught) { // same handling } Still, even in this case, I'd flag it up in a code review, as same handling for 3 different exception types generally (not always) means something is really wrong. So we are building a feature for a 1% case that, while simple to implement at parser-level in PHP, requires changes in all userland libraries that do parsing or rely on the AST. Cheers, Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/ On 18 April 2016 at 01:08, Dan Ackroyd <danack@basereality.com> wrote:

Zeev Suraski

10 years ago
> -----Original Message----- > From: Marco Pivetta [mailto:ocramius@gmail.com] > Sent: Monday, April 18, 2016 4:41 PM > To: Dan Ackroyd <danack@basereality.com> > Cc: Bronisław Białek <after89@gmail.com>; PHP internals > <internals@lists.php.net> > Subject: Re: [PHP-DEV] [VOTE] Catching Multiple Exception Types > > Heya, > > I voted "NO" due to previous discussion. TL;DR: this is FAR off the 80/20 use- > case for a language syntax change. > > C&P from my answer elsewhere: > > The following typical example is something REALLY rare, and requiring a > parser change for it seems excessive: > > try { > // ... > } catch (InvalidArgumentException $e) { > // same handling > } catch (PDOException $e) { > // same handling > } catch (BadMethodCallException $e) { > // same handling > } > > These 3 exceptions usually result in separate handling anyway. If same > handling is needed, you can as usual extract a private method (if you are in a > class) and deal with it there: > > try { > // ... > } catch (InvalidArgumentException $e) { > $this->sameHandling($e); > } catch (PDOException $e) { > $this->sameHandling($e); > } catch (BadMethodCallException $e) { > $this->sameHandling($e); > } > > private function sameHandling(Throwable $caught) > { > // same handling > } > > Still, even in this case, I'd flag it up in a code review, as same handling for 3 > different exception types generally (not always) means something is really > wrong.
I voted 'yes', as I think that exception handling is one of the few cases where it would make sense to have similar (or completely identical) handling for very different object types. Emitting a meaningful error and cleaning up are likely to look very similar for potentially quite different exception types. The main thing that made me question whether it's truly necessary, is that the same can be said about catch (Exception $e), which could handle all exception types in a generic way. But I think it is not uncommon to have a situation where you'd want to group a certain number of known exception types in one group, and either propagate other exceptions or handle them differently. I do find this one quite harmless in terms of potential negative implications - so even if it's not exceptionally useful (no pun intended) - it's fairly intuitive, simple in implementation and doesn't have to deal with scalars. The generic union types RFC has much farther reaching implications in terms of influencing how code is likely to get written in the future, and IMHO not for the better. Zeev

Derick Rethans

10 years ago
On Mon, 18 Apr 2016, Marco Pivetta wrote:
> Heya, > > I voted "NO" due to previous discussion. TL;DR: this is FAR off the 80/20 > use-case for a language syntax change.
I'm with you on this one.
> C&P from my answer elsewhere: > > The following typical example is something REALLY rare, and requiring a > parser change for it seems excessive: > > try { > // ... > } catch (InvalidArgumentException $e) { > // same handling > } catch (PDOException $e) { > // same handling > } catch (BadMethodCallException $e) { > // same handling > } > > These 3 exceptions usually result in separate handling anyway. If same > handling is needed, you can as usual extract a private method (if you are > in a class) and deal with it there: > > try { > // ... > } catch (InvalidArgumentException $e) { > $this->sameHandling($e); > } catch (PDOException $e) { > $this->sameHandling($e); > } catch (BadMethodCallException $e) { > $this->sameHandling($e); > } > > private function sameHandling(Throwable $caught) > { > // same handling > }
Even this is a scary way to do it. As you don't know which exception class you have, how would you know how to handle each separately. You'd have to if ($caught instanceof InvalidArgumentException) etc to be sure that what you're doing with the object is allowed. If they are user defined exceptions, you should make them implement an interface, and do a "catch (IMyException $e)" to be sure you can handle the different exceptions in a consistent manor. And that already works. If they are exception classes defined by other libraries, then, as you say, it's very likely they should be handled differenty anyway. Or rather, very generically, which you can do with "catch (Exception $e)". cheers, Derick
-- http://derickrethans.nl | http://xdebug.org Like Xdebug? Consider a donation: http://xdebug.org/donate.php twitter: @derickr and @xdebug Posted with an email client that doesn't mangle email: alpine

Dan Ackroyd

10 years ago
On 18 April 2016 at 16:17, Derick Rethans <derick@php.net> wrote:
> If they are user defined exceptions, you should make them implement an > interface,
Even within one oranisation, getting different teams to agree to common interfaces is not always possible. But when the exceptions are originating in code that people don't have any control over (e.g. anything open source) then changing the exceptions to match your particular interface requirements is a completely impossible. For example will you and Marco start taking requests to add particular interfaces to any exceptions thrown by the code in the libraries you maintain? Derick wrote:
> As you don't know which exception > class you have, how would you know how to handle each separately.
Why do you think you need to know how to handle each separately? If you ever need to do something different based on the type of exception, you probably wouldn't be using the multiple-catch. The multiple-catch is almost only ever going to be used when you have a list of exceptions where you're going to be doing the same thing for each. e.g. for retrying an operation: $success = false; while ($attempts < 4) { try { foo(); $success = true; } catch (NetworkException | DBDeadLockException $e) { // Save exceptions to add to surpressed exception // when that RFC is hopefully passed. $attempts++; } // All other } //Check for success or run out of attempts here. For NetworkException and DBDeadLockException we're always going to do the same thing; retry the operation a couple of times. For any exception type where we would want to do something different......we'd just use a separate catch block. cheers Dan

Stas Malyshev

10 years ago
Hi!
> Even this is a scary way to do it. As you don't know which exception > class you have, how would you know how to handle each separately. You'd > have to if ($caught instanceof InvalidArgumentException) etc to be sure > that what you're doing with the object is allowed.
I just recently had a case where I used something similar in Java code, so I can tell when it's useful: - You call some complex object that can fail for a lot of reasons (like HTTP client talking to remote service) - It can fail with a number of exceptions because something went bad - i.e. network is bad, DNS is bad, server is down, service is too busy, your request syntax is wrong, request is correct but you're not allowed to execute it, etc. - These causes can be roughly separated into two groups - requests that we can and should repeat - e.g. "server too busy" situation, and those that we should not repeat, e.g. "bad syntax" situation - Furthermore, failures can be "soft" - fail this request but continue next ones in the batch, or "hard" - drop everything and call for help because something is seriously broken. In this situation, grouping exceptions - which, due to being from different layers of the complex object do not follow any single structure - becomes very convenient. Of course it is possible to write a long tree of copy-paste handlers for each of the three cases, but it's annoying. Much cleaner to have just three branches. And of course I don't want to do something like catch(Exception) because *shudder*...
> say, it's very likely they should be handled differenty anyway. Or > rather, very generically, which you can do with "catch (Exception $e)".
catch(Exception) is practically never a good idea, unless you really are able to handle *every* exception. Now in PHP that includes parse errors, type errors, etc. I don't think any random code - except for a very generic final error handler - would ever want to do that.
-- Stas Malyshev smalyshev@gmail.com

Zeev Suraski

10 years ago
> -----Original Message----- > From: Derick Rethans [mailto:derick@php.net] > Sent: Monday, April 18, 2016 6:18 PM > To: Marco Pivetta <ocramius@gmail.com> > Cc: Dan Ackroyd <danack@basereality.com>; Bronisław Białek > <after89@gmail.com>; PHP internals <internals@lists.php.net> > Subject: Re: [PHP-DEV] [VOTE] Catching Multiple Exception Types > > On Mon, 18 Apr 2016, Marco Pivetta wrote: > > > Heya, > > > > I voted "NO" due to previous discussion. TL;DR: this is FAR off the > > 80/20 use-case for a language syntax change. > > I'm with you on this one. > > > C&P from my answer elsewhere: > > > > The following typical example is something REALLY rare, and requiring > > a parser change for it seems excessive: > > > > try { > > // ... > > } catch (InvalidArgumentException $e) { > > // same handling > > } catch (PDOException $e) { > > // same handling > > } catch (BadMethodCallException $e) { > > // same handling > > } > > > > These 3 exceptions usually result in separate handling anyway. If same > > handling is needed, you can as usual extract a private method (if you > > are in a class) and deal with it there: > > > > try { > > // ... > > } catch (InvalidArgumentException $e) { > > $this->sameHandling($e); > > } catch (PDOException $e) { > > $this->sameHandling($e); > > } catch (BadMethodCallException $e) { > > $this->sameHandling($e); > > } > > > > private function sameHandling(Throwable $caught) > > { > > // same handling > > } > > Even this is a scary way to do it. As you don't know which exception class you > have, how would you know how to handle each separately.
In reality, you often don't truly 'handle' exceptions at all. Different exception types are very often just a slightly fancier and easier way to return errors, and it's actually very common and perfectly reasonable to handle groups of exceptions in an identical way. It's also extremely common to see exception handlers that don't pay any attention to the exception object itself. In fact, glancing through the Zend Framework 2 codebase and the Drupal 8 codebase, it seems that the exception object is rarely ever used. In Drupal 8, many catch blocks simply resort to return FALSE or TRUE. For example: try { return $router->match($path); } catch (ResourceNotFoundException $e) { return FALSE; } catch (ParamNotConvertedException $e) { return FALSE; } catch (AccessDeniedHttpException $e) { return FALSE; } catch (MethodNotAllowedException $e) { return FALSE; } } Note that this is in fact different from catch (Exception $e), and I'm guessing the author intentionally did not want to catch an exception the type of which she/he weren't expecting to get - and instead, have it propagate upwards. With this RFC approved, it would become: try { return $router->match($path); } catch (ResourceNotFoundException|ParamNotConvertedException|AccessDeniedHttpException|MethodNotAllowedException $e) { return FALSE; } Which is much, much nicer and readable. And I found this in the ZF2 codebase: } catch (DiRuntimeException $e) { if ($methodRequirementType & self::RESOLVE_STRICT) { //finally ( be aware to do at the end of flow) array_pop($this->currentDependencies); if (isset($alias)) { array_pop($this->currentAliasDependenencies); } // if this item was marked strict, // plus it cannot be resolve, and no value exist, bail out throw new Exception\MissingPropertyException( sprintf( 'Missing %s for parameter ' . $name . ' for ' . $class . '::' . $method, (($value[0] === null) ? 'value' : 'instance/object') ), $e->getCode(), $e ); } else { //finally ( be aware to do at the end of flow) array_pop($this->currentDependencies); if (isset($alias)) { array_pop($this->currentAliasDependenencies); } return false; } } catch (ServiceManagerException $e) { // Zend\ServiceManager\Exception\ServiceNotCreatedException if ($methodRequirementType & self::RESOLVE_STRICT) { //finally ( be aware to do at the end of flow) array_pop($this->currentDependencies); if (isset($alias)) { array_pop($this->currentAliasDependenencies); } // if this item was marked strict, // plus it cannot be resolve, and no value exist, bail out throw new Exception\MissingPropertyException( sprintf( 'Missing %s for parameter ' . $name . ' for ' . $class . '::' . $method, (($value[0] === null) ? 'value' : 'instance/object') ), $e->getCode(), $e ); } else { //finally ( be aware to do at the end of flow) array_pop($this->currentDependencies); if (isset($alias)) { array_pop($this->currentAliasDependenencies); } return false; } } To save everyone some time, the two code blocks for the two exception types are completely identical. I'm not sure why it wasn't function()alized, but regardless, it's another case where the new proposed feature would make perfect sense. I think saying that identical handling for multiple exception types is bad or is grounds for an urgent code review is fairly disconnected from reality in many if not most cases. Zeev

Patrick ALLAERT

10 years ago
Zeev, Le mar. 19 avr. 2016 à 11:56, Zeev Suraski <zeev@zend.com> a écrit :
> In Drupal 8, many catch blocks simply resort to return FALSE or TRUE. For > example: > > try { > return $router->match($path); > } > catch (ResourceNotFoundException $e) { > return FALSE; > } > catch (ParamNotConvertedException $e) { > return FALSE; > } > catch (AccessDeniedHttpException $e) { > return FALSE; > } > catch (MethodNotAllowedException $e) { > return FALSE; > } > } >
This looks much more like Exceptions used for not "Exceptional" cases: where Exceptions are preferred over a clean API and avoiding at all price if-conditions. One of the very reason I voted "no". It encourages people to replace any kind of condition with an exception, leading to less readable code and incomplete API.
> try { > return $router->match($path); > } > catch > (ResourceNotFoundException|ParamNotConvertedException|AccessDeniedHttpException|MethodNotAllowedException > $e) { > return FALSE; > } >
The "return FALSE" here on all those cases is just another way to ease silencing Exceptions which is often a bad idea (like @). It's also perfectly possible to make all those Exceptions implement a "MatchingFailedException" interface (even empty). This would even make the collection reusable AND extensible because new user Exception classes could implement that "MatchingFailedException" interface without the need to refactor code. Which is much, much nicer and readable. And I found this in the ZF2
> codebase: > > } catch (DiRuntimeException $e) { > if ($methodRequirementType & self::RESOLVE_STRICT) { > //finally ( be aware to do at the end of flow) > array_pop($this->currentDependencies); > if (isset($alias)) { > array_pop($this->currentAliasDependenencies); > } > // if this item was marked strict, > // plus it cannot be resolve, and no value exist, > bail out > throw new Exception\MissingPropertyException( > sprintf( > 'Missing %s for parameter ' . $name . ' > for ' . $class . '::' . $method, > (($value[0] === null) ? 'value' : > 'instance/object') > ), > $e->getCode(), > $e > ); > } else { > //finally ( be aware to do at the end of flow) > array_pop($this->currentDependencies); > if (isset($alias)) { > array_pop($this->currentAliasDependenencies); > } > return false; > } > } catch (ServiceManagerException $e) { > // > Zend\ServiceManager\Exception\ServiceNotCreatedException > if ($methodRequirementType & self::RESOLVE_STRICT) { > //finally ( be aware to do at the end of flow) > array_pop($this->currentDependencies); > if (isset($alias)) { > array_pop($this->currentAliasDependenencies); > } > // if this item was marked strict, > // plus it cannot be resolve, and no value exist, > bail out > throw new Exception\MissingPropertyException( > sprintf( > 'Missing %s for parameter ' . $name . ' > for ' . $class . '::' . $method, > (($value[0] === null) ? 'value' : > 'instance/object') > ), > $e->getCode(), > $e > ); > } else { > //finally ( be aware to do at the end of flow) > array_pop($this->currentDependencies); > if (isset($alias)) { > array_pop($this->currentAliasDependenencies); > } > return false; > } > } > > To save everyone some time, the two code blocks for the two exception > types are completely identical. I'm not sure why it wasn't > function()alized, but regardless, it's another case where the new proposed > feature would make perfect sense. > > I think saying that identical handling for multiple exception types is bad > or is grounds for an urgent code review is fairly disconnected from reality > in many if not most cases. > > Zeev > > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php
Patrick

Dan Ackroyd

10 years ago
On 20 April 2016 at 23:16, Patrick ALLAERT <patrickallaert@php.net> wrote:
> It's also perfectly possible to make all those Exceptions implement a > "MatchingFailedException" interface (even empty).
Apologies for continuing the conversation, even after the RFC is incredibly likely to pass, but; I don't understand that argument at all, and think I disagree with the philosophy behind it. If it was followed, it would mean that exception classes would need to be aware of how they are being used. When exception classes are defined in a library, and the exceptions are being caught in an applicaton, wouldn't adding applicaton specific interfaces to the libaries exception classes just violate the separation between the two? It doesn't seem to be good practice. It would also lead to multiple interfaces being added to each exception class. e.g. if in the application we had these catches in different places in the application: try { return foo(); } catch (ResourceNotFoundException $e) { return FALSE; } catch (ParamNotConvertedException $e) { return FALSE; } try { return bar(); } catch (ResourceNotFoundException $e) { return FALSE; } catch (AccessDeniedHttpException $e) { return FALSE; } try { return quux(); } catch (ResourceNotFoundException $e) { return FALSE; } catch (MethodNotAllowedException $e) { return FALSE; } Where we specifically want to catch ResourceNotFoundException and one other exception. If we followed your advice and added a specific interface to the exceptions for each of the separate combinations, so that we could catch just the interface we would end up with this: class ResourceNotFoundException extends \Exception implements FooExInterface, BarExInterface, QuuxExInterface {} i.e. having to add one interface per usage. That doesn't seem that great either....and again is altering a class based on how it's going to be used.
> It encourages people to replace any kind of condition with an exception, > leading to less readable code and incomplete API.
I don't think this is the right attitude to have when designing a progrmming language. Any feature that enables developers to write code that covers edge cases in their application is by it's very nature going to be possibly to use in a way most people consider inappropriate. It's much more productive to focus on making code/features that are useful when used sensibly. Worrying that people might use it in a way that you wouldn't recommend using it, is fundamentally the wrong problem to try to address when designing a language. I think that's true in general but particularly true in a language like PHP where one application might be programmed in a very different way to another application. What would be useful and appropriate for one of them would be could considered a horrendous hack* in another application. If we are limiting ourselves to adding features that are correct and appropriate to use in all applications, that is limiting ourselves to a subset of the features that people would want to have to cover all of their use-cases. cheers Dan Ack * For example, a lot of people strongly dislike annotations. A significant number of people use annotations in their applications. Neither groups is 'right' or 'wrong', but it would be wrong for the people who don't like annotations to say that annotations should never be used.

Patrick ALLAERT

10 years ago
Le ven. 22 avr. 2016 à 00:10, Dan Ackroyd <danack@basereality.com> a écrit :
> On 20 April 2016 at 23:16, Patrick ALLAERT <patrickallaert@php.net> wrote: > > > It's also perfectly possible to make all those Exceptions implement a > > "MatchingFailedException" interface (even empty). > > Apologies for continuing the conversation, even after the RFC is > incredibly likely to pass, but; I don't understand that argument at > all, and think I disagree with the philosophy behind it. > > If it was followed, it would mean that exception classes would need to > be aware of how they are being used. When exception classes are > defined in a library, and the exceptions are being caught in an > applicaton, wouldn't adding applicaton specific interfaces to the > libaries exception classes just violate the separation between the > two? It doesn't seem to be good practice. > > It would also lead to multiple interfaces being added to each > exception class. e.g. if in the application we had these catches in > different places in the application: > > > try { > return foo(); > } catch (ResourceNotFoundException $e) { > return FALSE; > } catch (ParamNotConvertedException $e) { > return FALSE; > } > > try { > return bar(); > } catch (ResourceNotFoundException $e) { > return FALSE; > } > catch (AccessDeniedHttpException $e) { > return FALSE; > } > > try { > return quux(); > } catch (ResourceNotFoundException $e) { > return FALSE; > } catch (MethodNotAllowedException $e) { > return FALSE; > } > > Where we specifically want to catch ResourceNotFoundException and one > other exception. If we followed your advice and added a specific > interface to the exceptions for each of the separate combinations, so > that we could catch just the interface we would end up with this: > > class ResourceNotFoundException extends \Exception implements > FooExInterface, BarExInterface, QuuxExInterface {} > > i.e. having to add one interface per usage. That doesn't seem that > great either....and again is altering a class based on how it's going > to be used. >
Your are absolutely right. I didn't meant one to modify libraries' or someone else's code, just the part you are in control of and that one can rely on Exception _interfaces_ rather than always use an Exception hierarchy solely based on inheritance. Transforming Exceptions is also rather common and doesn't require modifying external code.
> > It encourages people to replace any kind of condition with an exception, > > leading to less readable code and incomplete API. > > I don't think this is the right attitude to have when designing a > progrmming language. > > Any feature that enables developers to write code that covers edge > cases in their application is by it's very nature going to be possibly > to use in a way most people consider inappropriate. >
Sorry if someone found my attitude inappropriate or offensive. I just wanted to bring the attention that most of the Exception examples mentioned in this thread seems like wrong usage for Exceptions. There is just nothing more repeatable or common cases than "resource not found", "method not allowed", "access denied",... They are all non exceptional cases better handled without exceptions and with clean APIs reflecting the business logic of the application. e.g.: if (!$actionAccessValidator->check($user, $resource, $action)) { return false; } $action->run($resource); return true; Should be preferred over: try { // $action->run() combines here both access checking + the actual logic $action->run($resource, $user); } catch (AccessDeniedException $e) { return false; } return true; I'm not going to mention the advantage of decoupled code. My vote was mostly motivated by the absence of examples showing code that is well written and designed which can benefit ot this new language construct.
> It's much more productive to focus on making code/features that are > useful when used sensibly. Worrying that people might use it in a way > that you wouldn't recommend using it, is fundamentally the wrong > problem to try to address when designing a language. >
Yes, but my worries are that it can only or mostly be used in cases where the issues relies elsewhere, I would prefer to avoid giving a signal to developers that make them comfortable using bad design practices. Cheers, Patrick

Rowan Collins

10 years ago
Patrick ALLAERT wrote on 24/04/2016 14:18:
> There is just nothing more repeatable or common cases than "resource not > found", "method not allowed", "access denied",... They are all non > exceptional cases better handled without exceptions and with clean APIs > reflecting the business logic of the application.
I think part of the problem may be that non-trivial examples require rather a lot of context. An example that comes to my mind is if an application is combining a bunch of different libraries, and calling them all in sequence: try { $fooService->doStuff($data); $barService->doMoreStuff($data); $bazService->doDifferentStuff($data); } catch ( FooException | BarException | BazException $e ) { // We don't care which library threw the exception, the action is always the same throw new AppException($e); // Hide which libraries we're using to implement the function } Regards,
-- Rowan Collins [IMSoP]

Björn Larsson

10 years ago
Den 2016-04-25 kl. 11:49, skrev Rowan Collins:
> Patrick ALLAERT wrote on 24/04/2016 14:18: >> There is just nothing more repeatable or common cases than "resource not >> found", "method not allowed", "access denied",... They are all non >> exceptional cases better handled without exceptions and with clean APIs >> reflecting the business logic of the application. > > I think part of the problem may be that non-trivial examples require > rather a lot of context. An example that comes to my mind is if an > application is combining a bunch of different libraries, and calling > them all in sequence: > > try > { > $fooService->doStuff($data); > $barService->doMoreStuff($data); > $bazService->doDifferentStuff($data); > } > catch ( FooException | BarException | BazException $e ) > { > // We don't care which library threw the exception, the action is > always the same > throw new AppException($e); // Hide which libraries we're using to > implement the function > } > > Regards,
Good example, thats how I would use multi-catch. Regards //Björn Larsson

Bishop Bettini

10 years ago
On Mon, Apr 18, 2016 at 9:40 AM, Marco Pivetta <ocramius@gmail.com> wrote:
> > I voted "NO" due to previous discussion. TL;DR: this is FAR off the 80/20 > use-case for a language syntax change. > > ... > These 3 exceptions usually result in separate handling anyway. If same > handling is needed, you can as usual extract a private method (if you are > in a class) and deal with it there: > > ... > Still, even in this case, I'd flag it up in a code review, as same handling > for 3 different exception types generally (not always) means something is > really wrong. > > So we are building a feature for a 1% case that, while simple to implement > at parser-level in PHP, requires changes in all userland libraries that do > parsing or rely on the AST. >
I agree: the times this need arises are relatively rare, and in those cases functional encapsulation cleanly handles the repetition. I would vote no, were it not for one thing: union types <https://wiki.php.net/rfc/union_types>. catch constitutes a formal signature, exactly like a function signature. In my mind, this proposal and the union types proposal are intrinsically tied. If we have one, we must have the other. If not, we're inducing a sadness. Since I do see the need for union types, and since I believe the two must be accepted or rejected together, I voted yes.

Stas Malyshev

10 years ago
Hi!
> catch constitutes a formal signature, exactly like a function signature. In > my mind, this proposal and the union types proposal are intrinsically tied. > If we have one, we must have the other. If not, we're inducing a sadness.
This has nothing to do with union types. It is just a shortcut for repeated catch() clauses, it does not introduce anything into type system, capture existing use case, and doesn't make anything more complicated. While union types introduce a lot of new things, make everything dealing with types more complicated and the need for them, unless we import a real lot of other concepts like type pattern matching is quite unclear. So I don't think this has much in common.
-- Stas Malyshev smalyshev@gmail.com

Bishop Bettini

10 years ago
On Mon, Apr 18, 2016 at 8:54 PM, Stanislav Malyshev <smalyshev@gmail.com> wrote:
> Hi! > > > catch constitutes a formal signature, exactly like a function signature. > In > > my mind, this proposal and the union types proposal are intrinsically > tied. > > If we have one, we must have the other. If not, we're inducing a sadness. > > This has nothing to do with union types. It is just a shortcut for > repeated catch() clauses, it does not introduce anything into type > system, capture existing use case, and doesn't make anything more > complicated. While union types introduce a lot of new things, make > everything dealing with types more complicated and the need for them, > unless we import a real lot of other concepts like type pattern matching > is quite unclear. So I don't think this has much in common. >
It's about the perception of consistency. "Oh, I can do this! Neat:" function neat(Foo | Bar $a) { ... } "But I can't do this? WTF?" catch (FooException | BarException $ex) { ... } And vice-versa. The perception revolves around the fact that both appear to be signatured, regardless of how they're implemented in the engine.

Stas Malyshev

10 years ago
Hi!
> It's about the perception of consistency. "Oh, I can do this! Neat:" > > function neat(Foo | Bar $a) { ... }
You shouldn't be able to do this, because it makes no sense - why would a function accept two random types and only them? That's probably a bad design - it should be one type or two functions.
> "But I can't do this? WTF?" > > catch (FooException | BarException $ex) { ... }
But this makes total sense - you routinely catch more than one type of exceptions, just not you write it catch(Foo) ... catch(Bar...). You very rarely have functions that do exactly the same with two types, and only those. The usage is different. I do not think approach "it should look the same, no matter what the usage is" is right.
> And vice-versa. The perception revolves around the fact that both appear > to be signatured, regardless of how they're implemented in the engine.
But that's not what they do. Function says "I accept only parameter of this type, and it's an error if it's not". Catch says "I will process exception of this type, but if it's not, other catch clause may process it". You don't chain functions like that, but you do chain catch clauses. Again, different usage.
-- Stas Malyshev smalyshev@gmail.com

Larry Garfield

10 years ago
On 04/18/2016 08:28 PM, Stanislav Malyshev wrote:
> Hi! > >> It's about the perception of consistency. "Oh, I can do this! Neat:" >> >> function neat(Foo | Bar $a) { ... } > You shouldn't be able to do this, because it makes no sense - why would > a function accept two random types and only them? That's probably a bad > design - it should be one type or two functions.
I detailed use cases for both & and | here: http://news.php.net/php.internals/92335 Admittedly Foo|Bar is of limited use, although there is some use. Foo&Bar, however, has plenty of uses. I do agree with the sentiment that having Foo|Bar for catch but not for function signatures is going to confuse people. If nothing else, it will result in even more pressure to add union types. --Larry Garfield

Stas Malyshev

10 years ago
Hi!
> Admittedly Foo|Bar is of limited use, although there is some use. > Foo&Bar, however, has plenty of uses.
That is a different topic, not related to exceptions at all.
> I do agree with the sentiment that having Foo|Bar for catch but not for > function signatures is going to confuse people.
Confuse which people? Do people now are confused that you can write multiple catch clauses for same exception but can't write multiple function bodies for same parameter set? Are they confused by the fact that it's a fatal error to pass wrong type to a function but catch just silently ignores it? Are they confused that you can write function(string $a) but you can't catch(string $a)? I don't think so. I think people are pretty good at figuring out exceptions and function parameters are different things. Java has multiple catch and no expression types, and .net has the same after a fashion (with somewhat weird syntax) but programmers on neither wonder around confused expecting to find type calculus somewhere.
-- Stas Malyshev smalyshev@gmail.com

Zeev Suraski

10 years ago
> -----Original Message----- > From: Stanislav Malyshev [mailto:smalyshev@gmail.com] > Sent: Tuesday, April 19, 2016 8:11 AM > To: Larry Garfield <larry@garfieldtech.com>; internals@lists.php.net > Subject: Re: [PHP-DEV] [VOTE] Catching Multiple Exception Types > > > > I do agree with the sentiment that having Foo|Bar for catch but not > > for function signatures is going to confuse people. > > Confuse which people? Do people now are confused that you can write > multiple catch clauses for same exception but can't write multiple function > bodies for same parameter set? Are they confused by the fact that it's a fatal > error to pass wrong type to a function but catch just silently ignores it? Are > they confused that you can write function(string $a) but you can't catch(string > $a)? I don't think so. I think people are pretty good at figuring out exceptions > and function parameters are different things. Java has multiple catch and no > expression types, and .net has the same after a fashion (with somewhat weird > syntax) but programmers on neither wonder around confused expecting to > find type calculus somewhere.
I agree. But I think that if people are going to be confused and will pressure for consistency with functions, it's an exceptionally easy (again, no pun intended) to explain why they are in fact inherently different and why it makes sense here and doesn't make sense there. Zeev

Bishop Bettini

10 years ago
On Mon, Apr 18, 2016 at 9:28 PM, Stanislav Malyshev <smalyshev@gmail.com> wrote:
> Hi! > > > It's about the perception of consistency. "Oh, I can do this! Neat:" > > > > function neat(Foo | Bar $a) { ... } > > You shouldn't be able to do this, because it makes no sense - why would > a function accept two random types and only them? That's probably a bad > design - it should be one type or two functions. > > > "But I can't do this? WTF?" > > > > catch (FooException | BarException $ex) { ... } > > But this makes total sense - you routinely catch more than one type of > exceptions, just not you write it catch(Foo) ... catch(Bar...). You very > rarely have functions that do exactly the same with two types, and only > those. The usage is different. > > I do not think approach "it should look the same, no matter what the > usage is" is right. > > > And vice-versa. The perception revolves around the fact that both appear > > to be signatured, regardless of how they're implemented in the engine. > > But that's not what they do. Function says "I accept only parameter of > this type, and it's an error if it's not". Catch says "I will process > exception of this type, but if it's not, other catch clause may process > it". You don't chain functions like that, but you do chain catch > clauses. Again, different usage. >
// concrete example // My application uses two implementation of collection pattern: // one from eloquent and one from haldayne. I have a log method that // logs value of either collection implementation and catches the same // kind of invalid collection regardless of implementation // // in this example, both union types and multi-catch are available namespace Eloquent; class Collection implements Contract\Arrayable { public static function toArray() { if (! (is_array($this->data) || is_object($this->data))) { throw new Exception\UnexpectedValue; } // .... } } namespace Haldayne; class Map implements ToArray { public static function toArray() { if (! (is_array($this->data) || is_object($this->data))) { throw new \UnexpectedValueException; } // .... } } namespace Application; function log(Eloquent\Collection | Haldayne\Map $entity) { $logger->log($entity->toArray()); } try { log(Config::find()); } catch (Eloquent\Exception\UnexpectedValue | \UnexpectedValueException $ex) { die('Configuration is neither array nor object'); } There is a pleasant symmetry in all of this. As a developer, I handle both library's concrete collection implementations similarly for both happy and unhappy paths, all using a compact expression (Class1 | Class2). The engine helps me out here. Now, imagine multi-catch passes (as I expect), but union types does not. I would change my log function to this: function log($entity) { if ($entity instanceof Eloquent\Collection | $entity instanceof Haldayne\Map) { $logger->log($entity->toArray()); } else { throw new \InvalidArgumentException; } } "Ugh, this is much easier in multi-catch. If only functions took multiple types, this would be much simpler to write." Now imagine union types pass, but multi-catch doesn't. Then I'd change my catch: try { log(Config::find()); } catch (Eloquent\Exception\UnexpectedValue $ex) { die('Configuration is neither array nor object'); } catch (\UnexpectedValueException $ex) { die('Configuration is neither array nor object'); } "ugh, this is much easier in function types. If only I could catch multiple exception types in one block." So when I'm talking about "user confusion", I'm referring to a perception of inconsistency by userland developers. And, I think we'd be well-wise to avoid designing inconsistencies. So even though I consider both union types and multi-catch to be on the rare side of utility, I consider having one and not the other a worse situation than having neither.

Stas Malyshev

10 years ago
Hi!
> namespace Application; > function log(Eloquent\Collection | Haldayne\Map $entity) { > $logger->log($entity->toArray()); > }
This is bad design. It assumes that two completely unrelated classes have the same function and it means the same. If it's indeed true, we have a way to express it. It's called "interface". If it's not true, and you are just duck-typing, you should not pretend you use strict types, and drop types completely. You shouldn't try yo sit on both chairs at the same time and pretend you're using strict typing while in fact duck-typing.
> "Ugh, this is much easier in multi-catch. If only functions took > multiple types, this would be much simpler to write."
And it would be completely wrong, abuse of typing. If you mean to say "anything that has log method", you should either have interface or just get rid of the strictness and duck-type it and rely on PHP to throw exception if anything happens. Otherwise it is just a sloppy design.
-- Stas Malyshev smalyshev@gmail.com

Bishop Bettini

10 years ago
On Tue, Apr 19, 2016 at 2:13 PM, Stanislav Malyshev <smalyshev@gmail.com> wrote:
> Hi! > > > namespace Application; > > function log(Eloquent\Collection | Haldayne\Map $entity) { > > $logger->log($entity->toArray()); > > } > > This is bad design. It assumes that two completely unrelated classes > have the same function and it means the same.
Both of these exist in the wild, and both have identical function wrt toArray: reducing a set of collection-like things to an array of arrays.
> If it's indeed true, we > have a way to express it. It's called "interface".
Except these two are in separate libraries that cannot share an interface.
> > "Ugh, this is much easier in multi-catch. If only functions took > > multiple types, this would be much simpler to write." > > And it would be completely wrong, abuse of typing. If you mean to say > "anything that has [toArray] method", you should either have interface or > just get rid of the strictness and duck-type it and rely on PHP to throw > exception if anything happens. Otherwise it is just a sloppy design. >
An interface is not possible: separate libraries. Duck-typing is not appropriate: only these two specific classes are supported. Union types solve certain problems better than equivalent run-time checks. Multi-catch solves certain *other* problems better than equivalent many-catch. They are orthogonal, but the syntax is similar: *"into this block, accept this type or this type or ...."* I contend that this apparent similarity must be consistent, lest we earn another sadness.

Ivan Enderlin @ Hoa

10 years ago
Hello, Representing the Hoa project community, we have voted yes. Not everyone was agree (60% yes, 40% no), but my vote reflects the average. Main argument in favor of no: * Encourage badly designed API. Main argument in favor of yes: * This is not an edge case when dealing with badly designed API, so this is useful. Cheers. On 17/04/16 18:51, Bronisław Białek wrote:

Dmitry Stogov

10 years ago
Only one note. I would prefer to use "," instead of "|" as a class name separator. Especially if "Union types" are not accepted. The rest should be fine. Thanks. Dmitry. ________________________________________ From: Bronisław Białek <after89@gmail.com> Sent: Sunday, April 17, 2016 19:51 To: PHP internals Subject: [PHP-DEV] [VOTE] Catching Multiple Exception Types Hi Internals, We've opened the vote on https://wiki.php.net/rfc/multiple-catch Voting will end on 2015-05-01
-- Regards, Bronisław Białek.

Levi Morrison

10 years ago
On Tue, Apr 19, 2016 at 6:39 AM, Dmitry Stogov <dmitry@zend.com> wrote:
> Only one note. I would prefer to use "," instead of "|" as a class name separator. > Especially if "Union types" are not accepted. > The rest should be fine.
On the other hand if union types is accepted it makes no sense to use "," here.

Dmitry Stogov

10 years ago
On 04/19/2016 04:09 PM, Levi Morrison wrote:
> On Tue, Apr 19, 2016 at 6:39 AM, Dmitry Stogov <dmitry@zend.com> wrote: >> Only one note. I would prefer to use "," instead of "|" as a class name separator. >> Especially if "Union types" are not accepted. >> The rest should be fine. > On the other hand if union types is accepted it makes no sense to use "," here.
Right. Lets take a final decision about syntax, after "Unoin Types" voting (but before 7.1 release).

Zeev Suraski

10 years ago
> On 19 באפר׳ 2016, at 15:46, Dmitry Stogov <dmitry@zend.com> wrote: > > Only one note. I would prefer to use "," instead of "|" as a class name separator. > Especially if "Union types" are not accepted. > The rest should be fine. >
+1 from me as well on that. We could have that change as a separate RFC at a later time, once things are clearer with union types. Zeev

Pascal MARTIN

10 years ago
Le 17/04/2016 18:51, Bronisław Białek a écrit :
> We've opened the vote on https://wiki.php.net/rfc/multiple-catch
Hi, At AFUP, we would be +1 on this RFC. Basically: small feature that should not break existing code and can be useful in some situations. Thanks for your work on this!
-- Pascal MARTIN, AFUP - French UG http://php-internals.afup.org/