1
+ <?php
2
+
3
+
4
+ namespace PhpSchool \PHP8Appreciate \Exercise ;
5
+
6
+ use PhpParser \Node \Identifier ;
7
+ use PhpParser \Node \Stmt ;
8
+ use PhpParser \Node \Stmt \Function_ ;
9
+ use PhpParser \Node \UnionType ;
10
+ use PhpParser \NodeFinder ;
11
+ use PhpParser \Parser ;
12
+ use PhpSchool \PhpWorkshop \CodeInsertion ;
13
+ use PhpSchool \PhpWorkshop \Exercise \AbstractExercise ;
14
+ use PhpSchool \PhpWorkshop \Exercise \CliExercise ;
15
+ use PhpSchool \PhpWorkshop \Exercise \ExerciseInterface ;
16
+ use PhpSchool \PhpWorkshop \Exercise \ExerciseType ;
17
+ use PhpSchool \PhpWorkshop \Exercise \SubmissionPatchable ;
18
+ use PhpSchool \PhpWorkshop \ExerciseCheck \SelfCheck ;
19
+ use PhpSchool \PhpWorkshop \Input \Input ;
20
+ use PhpSchool \PhpWorkshop \Patch ;
21
+ use PhpSchool \PhpWorkshop \Result \Failure ;
22
+ use PhpSchool \PhpWorkshop \Result \ResultInterface ;
23
+ use PhpSchool \PhpWorkshop \Result \Success ;
24
+ use Faker \Generator as FakerGenerator ;
25
+
26
+ class UniteTheTypes extends AbstractExercise implements
27
+ ExerciseInterface,
28
+ CliExercise,
29
+ SelfCheck,
30
+ SubmissionPatchable
31
+ {
32
+
33
+ private float $ total ;
34
+
35
+ public function __construct (private Parser $ parser , private FakerGenerator $ faker )
36
+ {
37
+ }
38
+
39
+ public function getName (): string
40
+ {
41
+ return 'Unite The Types ' ;
42
+ }
43
+
44
+ public function getDescription (): string
45
+ {
46
+ return 'PHP 8 \'s union types ' ;
47
+ }
48
+
49
+ public function getType (): ExerciseType
50
+ {
51
+ return ExerciseType::CLI ();
52
+ }
53
+
54
+ public function getArgs (): array
55
+ {
56
+ $ numbers = array_map (
57
+ function (): string {
58
+ if ($ this ->faker ->boolean ) {
59
+ return $ this ->faker ->numberBetween (0 , 50 );
60
+ }
61
+ return $ this ->faker ->randomFloat (3 , 0 , 50 );
62
+ },
63
+ range (0 , random_int (5 , 15 ))
64
+ );
65
+
66
+ $ this ->total = array_sum ($ numbers );
67
+
68
+ return [$ numbers ];
69
+ }
70
+
71
+ public function getPatch (): Patch
72
+ {
73
+ $ code = <<<'CODE'
74
+ declare(strict_types=1);
75
+
76
+ $argv = array_map(function ($value) {
77
+ return match (true) {
78
+ (int) $value != (float) $value => (float) $value,
79
+ (bool) random_int(0, 1) => (int) $value,
80
+ default => (string) $value
81
+ };
82
+ }, $argv);
83
+ CODE;
84
+
85
+ $ casterInsertion = new CodeInsertion (CodeInsertion::TYPE_BEFORE , $ code );
86
+
87
+ return (new Patch ())->withInsertion ($ casterInsertion );
88
+ }
89
+
90
+ public function check (Input $ input ): ResultInterface
91
+ {
92
+ /** @var array<Stmt> $statements */
93
+ $ statements = $ this ->parser ->parse ((string ) file_get_contents ($ input ->getRequiredArgument ('program ' )));
94
+
95
+ /** @var Function_|null $adder */
96
+ $ adder = (new NodeFinder ())->findFirst ($ statements , function (\PhpParser \Node $ node ) {
97
+ return $ node instanceof Function_ && $ node ->name ->toString () === 'adder ' ;
98
+ });
99
+
100
+ if (null === $ adder ) {
101
+ return Failure::fromNameAndReason ($ this ->getName (), 'No function named adder was found ' );
102
+ }
103
+
104
+ if (!isset ($ adder ->params [0 ])) {
105
+ return Failure::fromNameAndReason ($ this ->getName (), 'Function adder has no parameters ' );
106
+ }
107
+
108
+ /** @var \PhpParser\Node\Param $firstParam */
109
+ $ firstParam = $ adder ->params [0 ];
110
+
111
+ if (!$ firstParam ->type instanceof UnionType) {
112
+ return Failure::fromNameAndReason (
113
+ $ this ->getName (),
114
+ 'Function adder does not use a union type for it \'s first param '
115
+ );
116
+ }
117
+
118
+ $ incorrectTypes = array_filter (
119
+ $ firstParam ->type ->types ,
120
+ fn ($ type ) => !$ type instanceof Identifier
121
+ );
122
+
123
+ if (count ($ incorrectTypes )) {
124
+ return Failure::fromNameAndReason (
125
+ $ this ->getName (),
126
+ 'Union type is incorrect, it should only accept the required types '
127
+ );
128
+ }
129
+
130
+ $ types = array_map (
131
+ fn (Identifier $ type ) => $ type ->__toString (),
132
+ $ firstParam ->type ->types
133
+ );
134
+
135
+ sort ($ types );
136
+
137
+ if ($ types !== ['float ' , 'int ' , 'string ' ]) {
138
+ return Failure::fromNameAndReason (
139
+ $ this ->getName (),
140
+ 'Union type is incorrect, it should only accept the required types '
141
+ );
142
+ }
143
+
144
+ return new Success ('Union type for adder is correct ' );
145
+ }
146
+ }
0 commit comments