Skip to content

Commit 41fdf81

Browse files
committed
Mixed type exercise
1 parent 8dd8a23 commit 41fdf81

16 files changed

+453
-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",

exercises/all-mixed-up/problem/problem.md

Whitespace-only changes.
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+
}

test/Exercise/AllMixedUpTest.php

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<?php
2+
3+
namespace PhpSchool\PHP8AppreciateTest\Exercise;
4+
5+
use PhpSchool\PHP8Appreciate\Exercise\AllMixedUp;
6+
use PhpSchool\PhpWorkshop\Application;
7+
use PhpSchool\PhpWorkshop\Result\Failure;
8+
use PhpSchool\PhpWorkshop\Result\FileComparisonFailure;
9+
use PhpSchool\PhpWorkshop\Result\FunctionRequirementsFailure;
10+
use PhpSchool\PhpWorkshop\TestUtils\WorkshopExerciseTest;
11+
12+
class AllMixedUpTest extends WorkshopExerciseTest
13+
{
14+
public function getExerciseClass(): string
15+
{
16+
return AllMixedUp::class;
17+
}
18+
19+
public function getApplication(): Application
20+
{
21+
return require __DIR__ . '/../../app/bootstrap.php';
22+
}
23+
24+
public function tearDown(): void
25+
{
26+
$this->removeSolutionAsset('param.log');
27+
parent::tearDown();
28+
}
29+
30+
public function testSuccessfulSolution(): void
31+
{
32+
$this->runExercise('solution-correct.php');
33+
34+
$this->assertVerifyWasSuccessful();
35+
}
36+
37+
public function testFailureWhenNoFunctionNamedLogParam(): void
38+
{
39+
$this->runExercise('no-log-param-function.php');
40+
41+
$this->assertVerifyWasNotSuccessful();
42+
43+
$this->assertResultsHasFailure(Failure::class, 'No function named logParameter was found');
44+
}
45+
46+
public function testFailureWhenLogParamFunctionHasNoParams(): void
47+
{
48+
$this->runExercise('no-function-params.php');
49+
50+
$this->assertVerifyWasNotSuccessful();
51+
52+
$this->assertResultsHasFailure(
53+
Failure::class,
54+
'Function logParameter has no parameters'
55+
);
56+
}
57+
58+
public function testFailureWhenLogParamFunctionFirstParamHasNoType(): void
59+
{
60+
$this->runExercise('no-type-param.php');
61+
62+
$this->assertVerifyWasNotSuccessful();
63+
64+
$this->assertResultsHasFailure(
65+
Failure::class,
66+
'Function logParameter has no type for it\'s for first parameter'
67+
);
68+
}
69+
70+
public function testFailureWhenLogParamFunctionFirstParamIsNotMixed(): void
71+
{
72+
$this->runExercise('no-mixed-type-param.php');
73+
74+
$this->assertVerifyWasNotSuccessful();
75+
76+
$this->assertResultsHasFailure(
77+
Failure::class,
78+
'Function logParameter does not use the mixed type for it\'s first param'
79+
);
80+
}
81+
82+
public function testFailureWhenNotUsingGetDebugTypeFunction(): void
83+
{
84+
$this->runExercise('no-get-debug-type-function.php');
85+
86+
$this->assertVerifyWasNotSuccessful();
87+
88+
$this->assertResultsHasFailureAndMatches(
89+
FunctionRequirementsFailure::class,
90+
function (FunctionRequirementsFailure $failure) {
91+
self::assertEquals(['get_debug_type'], $failure->getMissingFunctions());
92+
93+
return true;
94+
}
95+
);
96+
}
97+
98+
public function testFailureWhenLogFileNotWritten(): void
99+
{
100+
$this->runExercise('no-log-file-written.php');
101+
102+
$this->assertVerifyWasNotSuccessful();
103+
104+
$this->assertResultsHasFailure(
105+
Failure::class,
106+
'File: "param.log" does not exist'
107+
);
108+
}
109+
110+
public function testFailureWhenLogFileNotCorrect(): void
111+
{
112+
$this->runExercise('log-file-wrong.php');
113+
114+
$this->assertVerifyWasNotSuccessful();
115+
116+
$this->assertResultsHasFailureAndMatches(
117+
FileComparisonFailure::class,
118+
function (FileComparisonFailure $failure) {
119+
self::assertEquals('param.log', $failure->getFileName());
120+
121+
return true;
122+
}
123+
);
124+
}
125+
126+
public function testFailureWhenLogFileNotCorrectTimes(): void
127+
{
128+
$this->runExercise('log-file-wrong-times.php');
129+
130+
$this->assertVerifyWasNotSuccessful();
131+
132+
$this->assertResultsHasFailureAndMatches(
133+
FileComparisonFailure::class,
134+
function (FileComparisonFailure $failure) {
135+
self::assertEquals('param.log', $failure->getFileName());
136+
137+
return true;
138+
}
139+
);
140+
}
141+
}
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('i:s'),
10+
get_debug_type($parameter)
11+
),
12+
FILE_APPEND
13+
);
14+
}

0 commit comments

Comments
 (0)