Skip to content

Commit d6766b3

Browse files
committed
Initial static return exercise
1 parent bcd0812 commit d6766b3

File tree

6 files changed

+209
-0
lines changed

6 files changed

+209
-0
lines changed

app/bootstrap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use PhpSchool\PHP8Appreciate\Exercise\InfiniteDivisions;
2828
use PhpSchool\PHP8Appreciate\Exercise\PhpPromotion;
2929
use PhpSchool\PHP8Appreciate\Exercise\LordOfTheStrings;
30+
use PhpSchool\PHP8Appreciate\Exercise\TheReturnOfStatic;
3031
use PhpSchool\PHP8Appreciate\Exercise\UniteTheTypes;
3132
use PhpSchool\PhpWorkshop\Application;
3233

@@ -41,6 +42,7 @@
4142
$app->addExercise(InfiniteDivisions::class);
4243
$app->addExercise(ASafeSpaceForNulls::class);
4344
$app->addExercise(AllMixedUp::class);
45+
$app->addExercise(TheReturnOfStatic::class);
4446

4547
$art = <<<ART
4648
_ __ _

app/config.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PhpSchool\PHP8Appreciate\Exercise\InfiniteDivisions;
99
use PhpSchool\PHP8Appreciate\Exercise\PhpPromotion;
1010
use PhpSchool\PHP8Appreciate\Exercise\LordOfTheStrings;
11+
use PhpSchool\PHP8Appreciate\Exercise\TheReturnOfStatic;
1112
use PhpSchool\PHP8Appreciate\Exercise\UniteTheTypes;
1213
use Psr\Container\ContainerInterface;
1314

@@ -44,5 +45,8 @@
4445
},
4546
AllMixedUp::class => function (ContainerInterface $c) {
4647
return new AllMixedUp($c->get(PhpParser\Parser::class), $c->get(\Faker\Generator::class));
48+
},
49+
TheReturnOfStatic::class => function (ContainerInterface $c) {
50+
return new TheReturnOfStatic($c->get(PhpParser\Parser::class));
4751
}
4852
];
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
class File
4+
{
5+
private ?string $permissions = null;
6+
public function withPermissions(string $permissions): static
7+
{
8+
$clone = new self();
9+
$clone->permissions = $permissions;
10+
return $clone;
11+
}
12+
}
13+
14+
class Image extends File
15+
{
16+
private ?string $ext = null;
17+
private ?string $crop = null;
18+
19+
public function withExt(string $ext): static
20+
{
21+
$clone = clone $this;
22+
$clone->ext = $ext;
23+
return $clone;
24+
}
25+
26+
public function withCrop(string $crop): static
27+
{
28+
$clone = clone $this;
29+
$clone->crop = $crop;
30+
return $clone;
31+
}
32+
}
33+
$image = (new Image())
34+
->withPermissions('w+')
35+
->withExt('jpeg')
36+
->withCrop('16x9');
37+
38+
var_dump($image);

exercises/the-return-of-static/problem/problem.md

Whitespace-only changes.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
class File
4+
{
5+
private ?string $permissions = null;
6+
public function withPermissions(string $permissions): static
7+
{
8+
$clone = clone $this;
9+
$clone->permissions = $permissions;
10+
return $clone;
11+
}
12+
}
13+
14+
class Image extends File
15+
{
16+
private ?string $ext = null;
17+
private ?string $crop = null;
18+
19+
public function withExt(string $ext): static
20+
{
21+
$clone = clone $this;
22+
$clone->ext = $ext;
23+
return $clone;
24+
}
25+
26+
public function withCrop(string $crop): static
27+
{
28+
$clone = clone $this;
29+
$clone->crop = $crop;
30+
return $clone;
31+
}
32+
}
33+
$image = (new Image())
34+
->withPermissions('w+')
35+
->withExt('jpeg')
36+
->withCrop('16x9');
37+
38+
var_dump($image);

src/Exercise/TheReturnOfStatic.php

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
namespace PhpSchool\PHP8Appreciate\Exercise;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\Assign;
7+
use PhpParser\Node\Expr\Clone_;
8+
use PhpParser\Node\Expr\Variable;
9+
use PhpParser\Node\Stmt;
10+
use PhpParser\Node\Stmt\ClassMethod;
11+
use PhpParser\Node\Stmt\Expression;
12+
use PhpParser\Node\Stmt\Function_;
13+
use PhpParser\NodeFinder;
14+
use PhpParser\Parser;
15+
use PhpSchool\PhpWorkshop\Exercise\AbstractExercise;
16+
use PhpSchool\PhpWorkshop\Exercise\CliExercise;
17+
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
18+
use PhpSchool\PhpWorkshop\Exercise\ExerciseType;
19+
use PhpSchool\PhpWorkshop\Exercise\ProvidesInitialCode;
20+
use PhpSchool\PhpWorkshop\ExerciseCheck\SelfCheck;
21+
use PhpSchool\PhpWorkshop\Input\Input;
22+
use PhpSchool\PhpWorkshop\Result\Failure;
23+
use PhpSchool\PhpWorkshop\Result\ResultInterface;
24+
use PhpSchool\PhpWorkshop\Result\Success;
25+
use PhpSchool\PhpWorkshop\Solution\SingleFileSolution;
26+
use PhpSchool\PhpWorkshop\Solution\SolutionInterface;
27+
28+
class TheReturnOfStatic extends AbstractExercise implements
29+
ExerciseInterface,
30+
ProvidesInitialCode,
31+
CliExercise,
32+
SelfCheck
33+
{
34+
public function __construct(private Parser $parser)
35+
{
36+
}
37+
38+
public function getName(): string
39+
{
40+
return 'The Return of Static';
41+
}
42+
43+
public function getDescription(): string
44+
{
45+
return 'PHP 8\'s static return types';
46+
}
47+
48+
public function getInitialCode(): SolutionInterface
49+
{
50+
return SingleFileSolution::fromFile(
51+
__DIR__ . '/../../exercises/the-return-of-static/initial/the-return-of-static.php'
52+
);
53+
}
54+
55+
public function getType(): ExerciseType
56+
{
57+
return ExerciseType::CLI();
58+
}
59+
60+
public function getArgs(): array
61+
{
62+
return [];
63+
}
64+
65+
public function check(Input $input): ResultInterface
66+
{
67+
/** @var array<Stmt> $statements */
68+
$statements = $this->parser->parse((string) file_get_contents($input->getRequiredArgument('program')));
69+
70+
$finder = new NodeFinder();
71+
72+
/** @var Function_|null $adder */
73+
$class = $finder->findFirst($statements, function (Node $node) {
74+
return $node instanceof Stmt\Class_ && $node->name->toString() === 'File';
75+
});
76+
77+
/** @var ClassMethod $method */
78+
$method = $finder->findFirst([$class], function (Node $node) {
79+
return $node instanceof ClassMethod && $node->name->toString() === 'withPermissions';
80+
});
81+
82+
if (!$class || !$method) {
83+
return new Failure($this->getName(), 'The method withPermissions cannot be found');
84+
}
85+
86+
87+
if (!($assign = $this->findAssignOnFirstLine($method))) {
88+
return new Failure($this->getName(), 'The first statement in withPermissions is not an assign');
89+
}
90+
91+
if ($this->isCloneOfThis($assign)) {
92+
return new Success($this->getName());
93+
}
94+
95+
return new Failure($this->getName(), 'The first statement is not a clone of `$this`');
96+
}
97+
98+
private function findAssignOnFirstLine(ClassMethod $method): ?Assign
99+
{
100+
if (!isset($method->stmts[0])) {
101+
return null;
102+
}
103+
104+
if (!$method->stmts[0] instanceof Expression) {
105+
return null;
106+
}
107+
108+
if (!$method->stmts[0]->expr instanceof Assign) {
109+
return null;
110+
}
111+
112+
return $method->stmts[0]->expr;
113+
}
114+
115+
private function isCloneOfThis(Assign $assign): bool
116+
{
117+
if (!$assign->expr instanceof Clone_) {
118+
return false;
119+
}
120+
121+
if (!$assign->expr->expr instanceof Variable) {
122+
return false;
123+
}
124+
125+
return $assign->expr->expr->name === 'this';
126+
}
127+
}

0 commit comments

Comments
 (0)