Skip to content

Commit bcd0812

Browse files
authored
Merge pull request #26 from php-school/mixed
2 parents 8dd8a23 + 51bcf16 commit bcd0812

16 files changed

+509
-1
lines changed

app/bootstrap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
throw new RuntimeException('Unable to locate Composer autoloader; please run "composer install".');
2020
}
2121

22+
use PhpSchool\PHP8Appreciate\Exercise\AllMixedUp;
2223
use PhpSchool\PHP8Appreciate\Exercise\AMatchMadeInHeaven;
2324
use PhpSchool\PHP8Appreciate\Exercise\ASafeSpaceForNulls;
2425
use PhpSchool\PHP8Appreciate\Exercise\CautionWithCatches;
@@ -39,6 +40,7 @@
3940
$app->addExercise(UniteTheTypes::class);
4041
$app->addExercise(InfiniteDivisions::class);
4142
$app->addExercise(ASafeSpaceForNulls::class);
43+
$app->addExercise(AllMixedUp::class);
4244

4345
$art = <<<ART
4446
_ __ _

app/config.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22

3+
use PhpSchool\PHP8Appreciate\Exercise\AllMixedUp;
34
use PhpSchool\PHP8Appreciate\Exercise\AMatchMadeInHeaven;
45
use PhpSchool\PHP8Appreciate\Exercise\ASafeSpaceForNulls;
56
use PhpSchool\PHP8Appreciate\Exercise\CautionWithCatches;
@@ -41,4 +42,7 @@
4142
ASafeSpaceForNulls::class => function (ContainerInterface $c) {
4243
return new ASafeSpaceForNulls($c->get(PhpParser\Parser::class), $c->get(\Faker\Generator::class));
4344
},
45+
AllMixedUp::class => function (ContainerInterface $c) {
46+
return new AllMixedUp($c->get(PhpParser\Parser::class), $c->get(\Faker\Generator::class));
47+
}
4448
];

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
],
2222
"require": {
2323
"php": "^8.0",
24-
"php-school/php-workshop": "dev-master"
24+
"php-school/php-workshop": "dev-file-comparison-regex-strip"
2525
},
2626
"require-dev": {
2727
"phpunit/phpunit": "^9",
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
As alluded to earlier in `Unite The Types` PHP's type system has been constantly and consistently growing for a long time.
2+
3+
PHP8 comes with a new type `mixed` which is essentially a union of `array|bool|callable|int|float|object|resource|string|null`.
4+
5+
This means that any value which can be produced in PHP will pass the check. Which is exactly the same as no type check.
6+
7+
I guess you're wondering why you would use it, right?
8+
9+
If anything - it signals intent. Your function really does accept anything. It's not just missing type information.
10+
11+
----------------------------------------------------------------------
12+
Create a program which contains one function named `logParameter`. It should have one parameter, and it's type must be `mixed`.
13+
14+
Within your function you should log the type of the parameter that was passed. For this, you can use a new PHP8 function called `get_debug_type`.
15+
16+
This function will give you a string identifier representing the type of any PHP variable.
17+
18+
You should log the parameter to a file named `param.log` next to your PHP script.
19+
20+
We will call this function multiple times when we verify it, so make sure you append to the file instead of overwriting it with each log.
21+
22+
Each log entry should be on a new line and should follow the format: `10:04:06: Got: string` where `10:04:06` is the time: hours, minutes & seconds and where `string` is the type, the result of `get_debug_type`.
23+
24+
The format of the line is very important, including the time.
25+
26+
You do not need to call the function, just define it. We will call it for you, with every PHP value we can think of!
27+
28+
### The advantages of the mixed type
29+
30+
* Allows us to indicate that the type has not been forgotten, it just cannot be specified more precisely.
31+
* Allows us to move information from phpdoc into function signatures.
32+
----------------------------------------------------------------------
33+
## HINTS
34+
35+
The function you implement must be called `logParameter`.
36+
37+
`file_put_contents` has a handy flag to append to a file instead of creating it anew: `FILE_APPEND`.
38+
39+
We will call your function automatically with a bunch of different types to make sure that it can accept anything.
40+
41+
We will specifically check that you used the `mixed` type. Omitting the type will not pass.
42+
43+
Documentation on the `mixed` type can be found by pointing your browser here:
44+
[https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.mixed]()
45+
46+
Documentation on `get_debug_type` can be found by pointing your browser here:
47+
[https://www.php.net/manual/en/function.get-debug-type.php]()
48+
49+
----------------------------------------------------------------------
50+
## EXTRA
51+
52+
You might want to delete or empty your log file each time your program starts, otherwise it will grow and grow and the comparison will fail.
53+
54+
Think about the return type of your `adder` function - you could declare it as `void`.
55+
56+
If you are curious how we compare the output of your program when it includes time, we simply check that it matches the format, rather than comparing exactly. More specifically, we use the regex `/\d{2}:\d{2}:\d{2}/`.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
function logParameter(mixed $parameter): void
4+
{
5+
file_put_contents(
6+
'param.log',
7+
sprintf(
8+
"%s: Got: %s\n",
9+
(new \DateTime())->format('H:i:s'),
10+
get_debug_type($parameter)
11+
),
12+
FILE_APPEND
13+
);
14+
}

src/Exercise/AllMixedUp.php

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<?php
2+
3+
namespace PhpSchool\PHP8Appreciate\Exercise;
4+
5+
use Faker\Generator as FakerGenerator;
6+
use PhpParser\BuilderFactory;
7+
use PhpParser\Node\Expr\Assign;
8+
use PhpParser\Node\Identifier;
9+
use PhpParser\Node\Stmt;
10+
use PhpParser\Node\Stmt\Expression;
11+
use PhpParser\Node\Stmt\Foreach_;
12+
use PhpParser\Node\Stmt\Function_;
13+
use PhpParser\NodeFinder;
14+
use PhpParser\Parser;
15+
use PhpSchool\PhpWorkshop\Check\FileComparisonCheck;
16+
use PhpSchool\PhpWorkshop\Check\FunctionRequirementsCheck;
17+
use PhpSchool\PhpWorkshop\Exercise\AbstractExercise;
18+
use PhpSchool\PhpWorkshop\Exercise\CliExercise;
19+
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
20+
use PhpSchool\PhpWorkshop\Exercise\ExerciseType;
21+
use PhpSchool\PhpWorkshop\Exercise\SubmissionPatchable;
22+
use PhpSchool\PhpWorkshop\ExerciseCheck\FileComparisonExerciseCheck;
23+
use PhpSchool\PhpWorkshop\ExerciseCheck\FunctionRequirementsExerciseCheck;
24+
use PhpSchool\PhpWorkshop\ExerciseCheck\SelfCheck;
25+
use PhpSchool\PhpWorkshop\ExerciseDispatcher;
26+
use PhpSchool\PhpWorkshop\Input\Input;
27+
use PhpSchool\PhpWorkshop\Patch;
28+
use PhpSchool\PhpWorkshop\Result\Failure;
29+
use PhpSchool\PhpWorkshop\Result\ResultInterface;
30+
use PhpSchool\PhpWorkshop\Result\Success;
31+
32+
class AllMixedUp extends AbstractExercise implements
33+
ExerciseInterface,
34+
CliExercise,
35+
SubmissionPatchable,
36+
FunctionRequirementsExerciseCheck,
37+
FileComparisonExerciseCheck,
38+
SelfCheck
39+
{
40+
private ?Patch $patch = null;
41+
42+
public function __construct(private Parser $parser, private FakerGenerator $faker)
43+
{
44+
}
45+
46+
public function getName(): string
47+
{
48+
return 'All Mixed Up';
49+
}
50+
51+
public function getDescription(): string
52+
{
53+
return 'PHP 8\'s mixed type';
54+
}
55+
56+
public function getType(): ExerciseType
57+
{
58+
return ExerciseType::CLI();
59+
}
60+
61+
public function configure(ExerciseDispatcher $dispatcher): void
62+
{
63+
$dispatcher->requireCheck(FunctionRequirementsCheck::class);
64+
$dispatcher->requireCheck(FileComparisonCheck::class);
65+
}
66+
67+
public function getArgs(): array
68+
{
69+
return [];
70+
}
71+
72+
public function getPatch(): Patch
73+
{
74+
if ($this->patch) {
75+
return $this->patch;
76+
}
77+
78+
$factory = new BuilderFactory();
79+
80+
$statements = [
81+
new Expression(
82+
new Assign(
83+
$factory->var('items'),
84+
$factory->val([
85+
$this->faker->word(),
86+
$this->faker->randomNumber(3),
87+
$this->faker->words($this->faker->numberBetween(3, 7)),
88+
$this->faker->randomFloat(),
89+
$this->faker->boolean(),
90+
null,
91+
$factory->new('stdClass')
92+
])
93+
)
94+
)
95+
];
96+
97+
$statements[] = new Foreach_(
98+
$factory->var('items'),
99+
$factory->var('item'),
100+
[
101+
'stmts' => [
102+
new Expression(
103+
$factory->funcCall(
104+
'logParameter',
105+
$factory->args([$factory->var('item')])
106+
)
107+
)
108+
]
109+
]
110+
);
111+
112+
return $this->patch = (new Patch())
113+
->withTransformer(function (array $originalStatements) use ($statements) {
114+
return array_merge($originalStatements, $statements);
115+
});
116+
}
117+
118+
public function getFilesToCompare(): array
119+
{
120+
return [
121+
'param.log' => [
122+
'strip' => '/\d{2}:\d{2}:\d{2}/' //strip out time strings
123+
]
124+
];
125+
}
126+
127+
public function getRequiredFunctions(): array
128+
{
129+
return ['get_debug_type'];
130+
}
131+
132+
public function getBannedFunctions(): array
133+
{
134+
return [];
135+
}
136+
137+
public function check(Input $input): ResultInterface
138+
{
139+
/** @var array<Stmt> $statements */
140+
$statements = $this->parser->parse((string) file_get_contents($input->getRequiredArgument('program')));
141+
142+
/** @var Function_|null $logger */
143+
$logger = (new NodeFinder())->findFirst($statements, function (\PhpParser\Node $node) {
144+
return $node instanceof Function_ && $node->name->toString() === 'logParameter';
145+
});
146+
147+
if (null === $logger) {
148+
return Failure::fromNameAndReason($this->getName(), 'No function named logParameter was found');
149+
}
150+
151+
if (!isset($logger->params[0])) {
152+
return Failure::fromNameAndReason($this->getName(), 'Function logParameter has no parameters');
153+
}
154+
155+
if ($logger->params[0]->type === null) {
156+
return Failure::fromNameAndReason(
157+
$this->getName(),
158+
'Function logParameter has no type for it\'s for first parameter'
159+
);
160+
}
161+
162+
$type = $logger->params[0]->type;
163+
164+
if (!$type instanceof Identifier || $type->name !== 'mixed') {
165+
return Failure::fromNameAndReason(
166+
$this->getName(),
167+
'Function logParameter does not use the mixed type for it\'s first param'
168+
);
169+
}
170+
171+
return new Success($this->getName());
172+
}
173+
}

0 commit comments

Comments
 (0)