rfc:sealed_classes
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
rfc:sealed_classes [2022/03/01 03:48] – azjezz | rfc:sealed_classes [2022/03/17 03:54] – start voting azjezz | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Sealed Classes ====== | ====== PHP RFC: Sealed Classes ====== | ||
* Date: 2021-04-24 | * Date: 2021-04-24 | ||
- | * Author: Saif Eddin Gmati < | + | * Author: Saif Eddin Gmati < |
* Status: Under Discussion | * Status: Under Discussion | ||
* Target Version: PHP 8.2 | * Target Version: PHP 8.2 | ||
Line 49: | Line 49: | ||
{ | { | ||
return match(true) { | return match(true) { | ||
- | $result instanceof | + | $result instanceof Success => $result-> |
- | $result instanceof | + | $result instanceof Failure => throw $result-> |
}; // no need for default, it's not possible. | }; // no need for default, it's not possible. | ||
} | } | ||
Line 142: | Line 142: | ||
sealed class Bar permits { } | sealed class Bar permits { } | ||
</ | </ | ||
+ | |||
+ | ==== Why not composite type aliases ==== | ||
+ | |||
+ | Some have suggested that the use of composite type aliases could solve the same problem, such as: | ||
+ | |||
+ | <code php> | ||
+ | <?php | ||
+ | |||
+ | final class Success { ... } | ||
+ | final class Failure { ... } | ||
+ | |||
+ | type Result = Success | Failure; | ||
+ | |||
+ | function foo(): Result { ... } | ||
+ | |||
+ | $result = foo(); | ||
+ | if ($result instanceof Success) { | ||
+ | // $result is Success | ||
+ | } else { | ||
+ | // $result is Failure | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | However, a type alias of N types, is not the same as a sealed class that permits N sub-types, as sealing offers 2 major differences. | ||
+ | |||
+ | 1. shared functionality | ||
+ | |||
+ | Sealed classes feature allow you to implement functionalities in the parent class, such as: | ||
+ | |||
+ | < | ||
+ | <?php | ||
+ | |||
+ | sealed abstract class Result permits Success, Failure | ||
+ | { | ||
+ | public function then(Closure $success, Closure $failure): Result | ||
+ | { | ||
+ | try { | ||
+ | $result = $this instanceof Success ? $success($this-> | ||
+ | |||
+ | return new Success($result); | ||
+ | } catch(Throwable $e) { | ||
+ | return new Failure($e); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | public function catch(Closure $failure): Result | ||
+ | { | ||
+ | return $this-> | ||
+ | } | ||
+ | |||
+ | public function map(Closure $success): Result | ||
+ | { | ||
+ | return $this-> | ||
+ | $success, | ||
+ | fn($exception) => throw $exception | ||
+ | ); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | final class Success extends Result { | ||
+ | public function __construct( | ||
+ | public readonly mixed $value, | ||
+ | ) {} | ||
+ | } | ||
+ | |||
+ | final class Failure extends Result { | ||
+ | public function __construct( | ||
+ | public readonly Throwable $throwable, | ||
+ | ) {} | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 2. the N+1 type. | ||
+ | |||
+ | Unlike a type alias, a sealed class is by itself a type. | ||
+ | |||
+ | Considering the following code which uses type aliases: | ||
+ | |||
+ | <code php> | ||
+ | final class B {} | ||
+ | final class C {} | ||
+ | |||
+ | type A = B|C; | ||
+ | </ | ||
+ | |||
+ | When you have a function defined as: | ||
+ | |||
+ | <code php> | ||
+ | function consumer(A $instance): void | ||
+ | { | ||
+ | echo $instance:: | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | The output can only be either `" | ||
+ | |||
+ | However, considering the following code which uses sealed classes feature: | ||
+ | |||
+ | <code php> | ||
+ | sealed class A permits B, C {} | ||
+ | final class B extends A {} | ||
+ | final class C extends A {} | ||
+ | </ | ||
+ | |||
+ | The output of `consumer` could be either `" | ||
===== Syntax ===== | ===== Syntax ===== | ||
- | Some people might be against introducing a new keyword | + | Some people might be against introducing a new keywords |
not being a valid class names anymore, therefor, a second vote will take place to decide which syntax should be used. | not being a valid class names anymore, therefor, a second vote will take place to decide which syntax should be used. | ||
Line 179: | Line 284: | ||
trait Corge for Grault, Garply {} | trait Corge for Grault, Garply {} | ||
</ | </ | ||
+ | |||
+ | |||
+ | ===== FAQ's ===== | ||
+ | |||
+ | == Wouldn' | ||
+ | |||
+ | No, a sealed class will always have a `permits` clauses, if a sealed class is defined without a `permits` clauses, it's considered | ||
+ | a compile error. | ||
+ | |||
+ | |||
+ | == Would PHP check if permitted classes exists when loading a sealed class? == | ||
+ | |||
+ | No, when loading a sealed class, PHP would treat just like any other class, and store the permitted types list to check against | ||
+ | later when another type tries to inherit from it. | ||
+ | |||
+ | == What if the permitted types don't actually extend the sealed type == | ||
+ | |||
+ | Example: | ||
+ | |||
+ | <code php> | ||
+ | sealed interface A permits B {} | ||
+ | |||
+ | class B {} | ||
+ | </ | ||
+ | |||
+ | This code would not produce any errors, as another type ( e.g: `C` ) could exist, in which it inherits from both `B`, and `A`, therefor, an instance of `A&B` could still exist. | ||
+ | |||
+ | == What if the permitted types don't actually extend the sealed type, and are final == | ||
+ | |||
+ | Example: | ||
+ | |||
+ | <code php> | ||
+ | sealed interface A permits B {} | ||
+ | |||
+ | final class B {} | ||
+ | </ | ||
+ | |||
+ | In this case, we would end up with an interface ' | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
Line 206: | Line 349: | ||
- | ===== Proposed Voting Choices | + | ===== Vote ===== |
As this is a language change, a 2/3 majority is required. | As this is a language change, a 2/3 majority is required. | ||
- | ===== Patches | + | Voting started on 2022-03-17 |
- | Links to any external patches and tests go here. | + | |
- | If there is no patch, make it clear who will create a patch, or whether a volunteer to help with implementation is needed. | + | <doodle title=" |
+ | * Yes | ||
+ | * No | ||
+ | </ | ||
- | Make it clear if the patch is intended to be the final patch, or is just a prototype. | + | A Second vote for the preferred syntax choice. |
- | For changes affecting the core language, | + | <doodle title=" |
+ | * `sealed` + `permits` | ||
+ | * `permits` only | ||
+ | * `for` | ||
+ | </ | ||
+ | ===== Patches and Tests ===== | ||
+ | |||
+ | Prototype patch using `for` syntax: https:// | ||
===== References ===== | ===== References ===== | ||
Line 226: | Line 378: | ||
* [[https:// | * [[https:// | ||
+ | ===== Changelog ===== | ||
+ | |||
+ | |||
+ | * 1.1: added comparison to composite types. | ||
+ | * 1.2: added FAQ's section. | ||
rfc/sealed_classes.txt · Last modified: 2022/04/01 08:21 by azjezz