====== PHP RFC: throw expression ====== * Date: 2020-03-21 * Author: Ilija Tovilo, tovilo.ilija@gmail.com * Target Version: PHP 8.0 * Status: Implemented * Implementation: https://github.com/php/php-src/pull/5279 ===== Introduction ===== Since in PHP ''throw'' is a statement it makes it impossible to throw exceptions in places where only expressions are allowed, like arrow functions, the coalesce operator and the ternary/elvis operator. This RFC proposes converting the ''throw'' statement into an expression so that these cases become possible. ===== Proposal ===== Allow using ''throw'' in any context where an expression is accepted. These examples come to mind that seem useful: // This was previously not possible since arrow functions only accept a single expression while throw was a statement. $callable = fn() => throw new Exception(); // $value is non-nullable. $value = $nullableValue ?? throw new InvalidArgumentException(); // $value is truthy. $value = $falsableValue ?: throw new InvalidArgumentException(); // $value is only set if the array is not empty. $value = !empty($array) ? reset($array) : throw new InvalidArgumentException(); There are other places where it could be used which are more controversial. These cases are allowed in this RFC. // An if statement could make the intention clearer $condition && throw new Exception(); $condition || throw new Exception(); $condition and throw new Exception(); $condition or throw new Exception(); ===== Operator precedence ===== If ''throw'' becomes an expression operator precedence becomes relevant. These examples are working today. throw $this->createNotFoundException(); // Evaluated as throw ($this->createNotFoundException()); // Instead of (throw $this)->createNotFoundException(); throw static::createNotFoundException(); // Evaluated as throw (static::createNotFoundException()); // Instead of (throw static)::createNotFoundException(); throw $userIsAuthorized ? new ForbiddenException() : new UnauthorizedException(); // Evaluated as throw ($userIsAuthorized ? new ForbiddenException() : new UnauthorizedException()); // Instead of (throw $userIsAuthorized) ? new ForbiddenException() : new UnauthorizedException(); throw $maybeNullException ?? new Exception(); // Evaluated as throw ($maybeNullException ?? new Exception()); // Instead of (throw $maybeNullException) ?? new Exception(); throw $exception = new Exception(); // Evaluated as throw ($exception = new Exception()); // Instead of (throw $exception) = new Exception(); throw $exception ??= new Exception(); // Evaluated as throw ($exception ??= new Exception()); // Instead of (throw $exception) ??= new Exception(); throw $condition1 && $condition2 ? new Exception1() : new Exception2(); // Evaluated as throw ($condition1 && $condition2 ? new Exception1() : new Exception2()); // Instead of (throw $condition1) && $condition2 ? new Exception1() : new Exception2(); The common theme here is that everything after the ''throw'' keyword has a higher precedence. For this reason this RFC proposes to use the lowest operator precedence possible. All the current code, even if broken or strange, will continue behaving the same way. This isn't a problem because generally ''throw'' should be the last operator you're using as every expression after it wouldn't be evaluated anyway. The only downside of the low precedence is that a ''throw'' between two short-circuit operators would not be possible without parentheses: $condition || throw new Exception('$condition must be truthy') && $condition2 || throw new Exception('$condition2 must be truthy'); // Evaluated as $condition || (throw new Exception('$condition must be truthy') && $condition2 || (throw new Exception('$condition2 must be truthy'))); // Instead of $condition || (throw new Exception('$condition must be truthy')) && $condition2 || (throw new Exception('$condition2 must be truthy')); But I see little use for code like this. ===== Backward Incompatible Changes ===== None, specifically because the lowest precedence was chosen. ===== Other languages ===== The same was implemented in C# 7.0 in 2017. ((https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/throw#the-throw-expression)) There aren't many other languages that allow this. There's an ECMAScript proposal because the same issues exist there. ((https://github.com/tc39/proposal-throw-expressions)) ===== Proposed PHP Version(s) ===== Proposed version is PHP 8. ===== Voting ===== Voting starts 2020-04-05 and ends 2020-04-19. As this is a language change, a 2/3 majority is required. The vote is a straight Yes/No vote for accepting the RFC and merging the patch. * Yes * No ===== References ===== ==== Old discussion in internals list ==== * https://externals.io/message/49569 (2010) * https://externals.io/message/15301 (2005) * https://externals.io/message/10553 (2004)