language/enumerations.xml
0f53e8f37b7bf8513c93e5e2d67e7787f26ed693
...
...
@@ -37,6 +37,7 @@
37
37
<programlisting role="php">
38
38
<![CDATA[
39
39
<?php
40
+

40
41
enum Suit
41
42
{
42
43
case Hearts;
...
...
@@ -59,14 +60,20 @@ enum Suit
59
60
<programlisting role="php">
60
61
<![CDATA[
61
62
<?php
62
-
function pick_a_card(Suit $suit) { ... }
63
+

64
+
function pick_a_card(Suit $suit)
65
+
{
66
+
/* ... */
67
+
}
63
68

64
69
$val = Suit::Diamonds;
65
70

66
71
// OK
67
72
pick_a_card($val);
73
+

68
74
// OK
69
75
pick_a_card(Suit::Clubs);
76
+

70
77
// TypeError: pick_a_card(): Argument #1 ($suit) must be of type Suit, string given
71
78
pick_a_card('Spades');
72
79
?>
...
...
@@ -91,6 +98,7 @@ pick_a_card('Spades');
91
98
<programlisting role="php">
92
99
<![CDATA[
93
100
<?php
101
+

94
102
$a = Suit::Spades;
95
103
$b = Suit::Spades;
96
104

...
...
@@ -104,7 +112,7 @@ $a instanceof Suit; // true
104
112
<para>
105
113
It also means that enum values are never <literal>&lt;</literal> or <literal>&gt;</literal> each other,
106
114
since those comparisons are not meaningful on objects. Those comparisons will always return
107
-
false when working with enum values.
115
+
&false; when working with enum values.
108
116
</para>
109
117

110
118
<para>
...
...
@@ -124,12 +132,20 @@ $a instanceof Suit; // true
124
132
<programlisting role="php">
125
133
<![CDATA[
126
134
<?php
135
+

127
136
print Suit::Spades->name;
128
137
// prints "Spades"
129
138
?>
130
139
]]>
131
140
</programlisting>
132
141

142
+
<para>
143
+
It is also possible to use the <function>defined</function> and <function>constant</function>
144
+
functions to check for the existence of or read an enum case if the name is obtained dynamically.
145
+
This is, however, discouraged as using <link linkend="language.enumerations.backed">Backed enums</link>
146
+
should work for most use cases.
147
+
</para>
148
+

133
149
</sect1>
134
150

135
151
<sect1 xml:id="language.enumerations.backed">
...
...
@@ -147,6 +163,7 @@ print Suit::Spades->name;
147
163
<programlisting role="php">
148
164
<![CDATA[
149
165
<?php
166
+

150
167
enum Suit: string
151
168
{
152
169
case Hearts = 'H';
...
...
@@ -188,6 +205,7 @@ enum Suit: string
188
205
<programlisting role="php">
189
206
<![CDATA[
190
207
<?php
208
+

191
209
print Suit::Clubs->value;
192
210
// Prints "C"
193
211
?>
...
...
@@ -202,6 +220,7 @@ print Suit::Clubs->value;
202
220
<programlisting role="php">
203
221
<![CDATA[
204
222
<?php
223
+

205
224
$suit = Suit::Clubs;
206
225
$ref = &$suit->value;
207
226
// Error: Cannot acquire reference to property Suit::$value
...
...
@@ -242,6 +261,7 @@ $ref = &$suit->value;
242
261
<programlisting role="php">
243
262
<![CDATA[
244
263
<?php
264
+

245
265
$record = get_stuff_from_database($id);
246
266
print $record['suit'];
247
267

...
...
@@ -272,6 +292,7 @@ print $suit->value;
272
292
<programlisting role="php">
273
293
<![CDATA[
274
294
<?php
295
+

275
296
interface Colorful
276
297
{
277
298
public function color(): string;
...
...
@@ -300,7 +321,10 @@ enum Suit implements Colorful
300
321
}
301
322
}
302
323

303
-
function paint(Colorful $c) { ... }
324
+
function paint(Colorful $c)
325
+
{
326
+
/* ... */
327
+
}
304
328

305
329
paint(Suit::Clubs); // Works
306
330

...
...
@@ -322,6 +346,7 @@ print Suit::Diamonds->shape(); // prints "Rectangle"
322
346
<programlisting role="php">
323
347
<![CDATA[
324
348
<?php
349
+

325
350
interface Colorful
326
351
{
327
352
public function color(): string;
...
...
@@ -353,7 +378,7 @@ enum Suit: string implements Colorful
353
378

354
379
<para>
355
380
Methods may be arbitrarily complex, but in practice will usually return a static value or
356
-
<link linkend="control-structures.match">match</link> on <literal>$this</literal> to provide
381
+
&match; on <literal>$this</literal> to provide
357
382
different results for different cases.
358
383
</para>
359
384

...
...
@@ -371,6 +396,7 @@ enum Suit: string implements Colorful
371
396
<programlisting role="php">
372
397
<![CDATA[
373
398
<?php
399
+

374
400
interface Colorful
375
401
{
376
402
public function color(): string;
...
...
@@ -426,6 +452,7 @@ final class Suit implements UnitEnum, Colorful
426
452
<programlisting role="php">
427
453
<![CDATA[
428
454
<?php
455
+

429
456
enum Size
430
457
{
431
458
case Small;
...
...
@@ -465,6 +492,7 @@ enum Size
465
492
<programlisting role="php">
466
493
<![CDATA[
467
494
<?php
495
+

468
496
enum Size
469
497
{
470
498
case Small;
...
...
@@ -490,6 +518,7 @@ enum Size
490
518
<programlisting role="php">
491
519
<![CDATA[
492
520
<?php
521
+

493
522
interface Colorful
494
523
{
495
524
public function color(): string;
...
...
@@ -544,30 +573,49 @@ enum Suit implements Colorful
544
573
<programlisting role="php">
545
574
<![CDATA[
546
575
<?php
576
+

547
577
// This is an entirely legal Enum definition.
548
578
enum Direction implements ArrayAccess
549
579
{
550
580
case Up;
551
581
case Down;
552
582

553
-
public function offsetGet($val) { ... }
554
-
public function offsetExists($val) { ... }
555
-
public function offsetSet($val) { throw new Exception(); }
556
-
public function offsetUnset($val) { throw new Exception(); }
583
+
public function offsetExists($offset): bool
584
+
{
585
+
return false;
586
+
}
587
+

588
+
public function offsetGet($offset): mixed
589
+
{
590
+
return null;
591
+
}
592
+

593
+
public function offsetSet($offset, $value): void
594
+
{
595
+
throw new Exception();
596
+
}
597
+

598
+
public function offsetUnset($offset): void
599
+
{
600
+
throw new Exception();
601
+
}
557
602
}
558
603

559
604
class Foo
560
605
{
561
606
// This is allowed.
562
-
const Bar = Direction::Down;
607
+
const DOWN = Direction::Down;
563
608

564
609
// This is disallowed, as it may not be deterministic.
565
-
const Bar = Direction::Up['short'];
610
+
const UP = Direction::Up['short'];
566
611
// Fatal error: Cannot use [] on enums in constant expression
567
612
}
568
613

569
614
// This is entirely legal, because it's not a constant expression.
570
615
$x = Direction::Up['short'];
616
+
var_dump("\$x is " . var_export($x, true));
617
+

618
+
$foo = new Foo();
571
619
?>
572
620
]]>
573
621
</programlisting>
...
...
@@ -585,8 +633,9 @@ $x = Direction::Up['short'];
585
633
<member>Constructors and Destructors are forbidden.</member>
586
634
<member>Inheritance is not supported. Enums may not extend or be extended.</member>
587
635
<member>Static or object properties are not allowed.</member>
588
-
<member>Cloning an Enum case is not supported, as cases must be singleton instances</member>
636
+
<member>Cloning an Enum case is not supported, as cases must be singleton instances.</member>
589
637
<member><link linkend="language.oop5.magic">Magic methods</link>, except for those listed below, are disallowed.</member>
638
+
<member>Enums must always be declared before they are used.</member>
590
639
</simplelist>
591
640

592
641
<para>The following object functionality is available, and behaves just as it does on any other object:</para>
...
...
@@ -624,8 +673,10 @@ $x = Direction::Up['short'];
624
673
<programlisting role="php">
625
674
<![CDATA[
626
675
<?php
676
+

627
677
$clovers = new Suit();
628
678
// Error: Cannot instantiate enum Suit
679
+

629
680
$horseshoes = (new ReflectionClass(Suit::class))->newInstanceWithoutConstructor()
630
681
// Error: Cannot instantiate enum Suit
631
682
?>
...
...
@@ -646,6 +697,7 @@ $horseshoes = (new ReflectionClass(Suit::class))->newInstanceWithoutConstructor(
646
697
<programlisting role="php">
647
698
<![CDATA[
648
699
<?php
700
+

649
701
Suit::cases();
650
702
// Produces: [Suit::Hearts, Suit::Diamonds, Suit::Clubs, Suit::Spades]
651
703
?>
...
...
@@ -667,6 +719,7 @@ Suit::cases();
667
719
<programlisting role="php">
668
720
<![CDATA[
669
721
<?php
722
+

670
723
Suit::Hearts === unserialize(serialize(Suit::Hearts));
671
724

672
725
print serialize(Suit::Hearts);
...
...
@@ -677,11 +730,11 @@ print serialize(Suit::Hearts);
677
730

678
731
<para>
679
732
On deserialization, if an enum and case cannot be found to match a serialized
680
-
value a warning will be issued and <literal>false</literal> returned.</para>
733
+
value a warning will be issued and &false; returned.</para>
681
734

682
735
<para>
683
736
If a Pure Enum is serialized to JSON, an error will be thrown. If a Backed Enum
684
-
is serialized to JSON, it will be represented by its value scalar only, in the
737
+
is serialized to JSON, it will be represented by its scalar value only, in the
685
738
appropriate type. The behavior of both may be overridden by implementing
686
739
<classname>JsonSerializable</classname>.
687
740
</para>
...
...
@@ -693,6 +746,7 @@ print serialize(Suit::Hearts);
693
746
<programlisting role="php">
694
747
<![CDATA[
695
748
<?php
749
+

696
750
enum Foo {
697
751
case Bar;
698
752
}
...
...
@@ -719,6 +773,105 @@ Baz Enum:int {
719
773
</programlisting>
720
774
</sect1>
721
775

776
+
<sect1 xml:id="language.enumerations.object-differences.inheritance">
777
+

778
+
<title>Why enums aren't extendable</title>
779
+

780
+
<simpara>
781
+
Classes have contracts on their methods:
782
+
</simpara>
783
+

784
+
<programlisting role="php">
785
+
<![CDATA[
786
+
<?php
787
+

788
+
class A {}
789
+
class B extends A {}
790
+

791
+
function foo(A $a) {}
792
+

793
+
function bar(B $b) {
794
+
foo($b);
795
+
}
796
+
?>
797
+
]]>
798
+
</programlisting>
799
+

800
+
<simpara>
801
+
This code is type-safe, as B follows the contract of A, and through the magic of
802
+
co/contra-variance, any expectation one may have of the methods will be
803
+
preserved, exceptions excepted.
804
+
</simpara>
805
+

806
+
<simpara>
807
+
Enums have contracts on their cases, not methods:
808
+
</simpara>
809
+

810
+
<programlisting role="php">
811
+
<![CDATA[
812
+
<?php
813
+

814
+
enum ErrorCode {
815
+
case SOMETHING_BROKE;
816
+
}
817
+

818
+
function quux(ErrorCode $errorCode)
819
+
{
820
+
// When written, this code appears to cover all cases
821
+
match ($errorCode) {
822
+
ErrorCode::SOMETHING_BROKE => true,
823
+
}
824
+
}
825
+

826
+
?>
827
+
]]>
828
+
</programlisting>
829
+

830
+
<simpara>
831
+
The &match; statement in the function <code>quux</code> can be static analyzed to cover
832
+
all of the cases in ErrorCode.
833
+
</simpara>
834
+

835
+
<simpara>
836
+
But imagine it was allowed to extend enums:
837
+
</simpara>
838
+

839
+

840
+
<programlisting role="php">
841
+
<![CDATA[
842
+
<?php
843
+

844
+
// Thought experiment code where enums are not final.
845
+
// Note, this won't actually work in PHP.
846
+
enum MoreErrorCode extends ErrorCode {
847
+
case PEBKAC;
848
+
}
849
+

850
+
function fot(MoreErrorCode $errorCode) {
851
+
quux($errorCode);
852
+
}
853
+

854
+
fot(MoreErrorCode::PEBKAC);
855
+

856
+
?>
857
+
]]>
858
+
</programlisting>
859
+

860
+
<simpara>
861
+
Under normal inheritance rules, a class that extends another will pass
862
+
the type check.
863
+
</simpara>
864
+

865
+
<simpara>
866
+
The problem would be that the &match; statement in <code>quux()</code> no longer covers all
867
+
the cases. Because it doesn't know about <code>MoreErrorCode::PEBKAC</code> the match will throw an exception.
868
+
</simpara>
869
+

870
+
<simpara>
871
+
Because of this enums are final and can't be extended.
872
+
</simpara>
873
+
</sect1>
874
+

722
875
<sect1 xml:id="language.enumerations.examples">
723
876
&reftitle.examples;
724
877

...
...
@@ -728,20 +881,24 @@ Baz Enum:int {
728
881
<programlisting role="php">
729
882
<![CDATA[
730
883
<?php
884
+

731
885
enum SortOrder
732
886
{
733
-
case ASC;
734
-
case DESC;
887
+
case Asc;
888
+
case Desc;
735
889
}
736
890

737
-
function query($fields, $filter, SortOrder $order = SortOrder::ASC) { ... }
891
+
function query($fields, $filter, SortOrder $order = SortOrder::Asc)
892
+
{
893
+
/* ... */
894
+
}
738
895
?>
739
896
]]>
740
897
</programlisting>
741
898
<para>
742
899
The <literal>query()</literal> function can now proceed safe in the knowledge that
743
-
<literal>$order</literal> is guaranteed to be either <literal>SortOrder::ASC</literal>
744
-
or <literal>SortOrder::DESC</literal>. Any other value would have resulted in a
900
+
<literal>$order</literal> is guaranteed to be either <literal>SortOrder::Asc</literal>
901
+
or <literal>SortOrder::Desc</literal>. Any other value would have resulted in a
745
902
<classname>TypeError</classname>, so no further error checking or testing is needed.
746
903
</para>
747
904
</example>
...
...
@@ -755,6 +912,7 @@ function query($fields, $filter, SortOrder $order = SortOrder::ASC) { ... }
755
912
<programlisting role="php">
756
913
<![CDATA[
757
914
<?php
915
+

758
916
enum UserStatus: string
759
917
{
760
918
case Pending = 'P';
...
...
@@ -792,6 +950,7 @@ enum UserStatus: string
792
950
<programlisting role="php">
793
951
<![CDATA[
794
952
<?php
953
+

795
954
foreach (UserStatus::cases() as $case) {
796
955
printf('<option value="%s">%s</option>\n', $case->value, $case->label());
797
956
}
798
957