Skip to content

Commit 6928368

Browse files
committed
Exercise setup
1 parent d655317 commit 6928368

File tree

14 files changed

+360
-42
lines changed

14 files changed

+360
-42
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\TheAttributesOfSuccess;
3031
use PhpSchool\PHP8Appreciate\Exercise\TheReturnOfStatic;
3132
use PhpSchool\PHP8Appreciate\Exercise\ThrowAnExpression;
3233
use PhpSchool\PHP8Appreciate\Exercise\UniteTheTypes;
@@ -47,6 +48,7 @@
4748
$app->addExercise(TheReturnOfStatic::class);
4849
$app->addExercise(ThrowAnExpression::class);
4950
$app->addExercise(StringifyToDemystify::class);
51+
$app->addExercise(TheAttributesOfSuccess::class);
5052

5153
$art = <<<ART
5254
_ __ _

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\TheAttributesOfSuccess;
1112
use PhpSchool\PHP8Appreciate\Exercise\TheReturnOfStatic;
1213
use PhpSchool\PHP8Appreciate\Exercise\ThrowAnExpression;
1314
use PhpSchool\PHP8Appreciate\Exercise\UniteTheTypes;
@@ -57,4 +58,7 @@
5758
StringifyToDemystify::class => function (ContainerInterface $c) {
5859
return new StringifyToDemystify($c->get(PhpParser\Parser::class));
5960
},
61+
TheAttributesOfSuccess::class => function (ContainerInterface $c) {
62+
return new TheAttributesOfSuccess($c->get(PhpParser\Parser::class), $c->get(\Faker\Generator::class));
63+
}
6064
];
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
#[Attribute(Attribute::TARGET_CLASS)]
4+
class Deserialize {
5+
6+
}
7+
8+
#[Attribute(Attribute::TARGET_PROPERTY)]
9+
class Map {
10+
public function __construct(public string $mapFrom)
11+
{
12+
}
13+
}
14+
15+
#[Attribute(Attribute::TARGET_PROPERTY)]
16+
class Skip {
17+
18+
}
Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,5 @@
11
<?php
22

3-
#[Attribute(Attribute::TARGET_CLASS)]
4-
class Deserialize {}
5-
6-
#[Attribute(Attribute::TARGET_PROPERTY)]
7-
class Map {
8-
public function __construct(public string $mapFrom)
9-
{
10-
}
11-
}
12-
13-
#[Attribute(Attribute::TARGET_PROPERTY)]
14-
class Skip {
15-
16-
}
17-
18-
#[Deserialize]
19-
class CityBreak {
20-
public string $country;
21-
22-
#[Map('town')]
23-
public string $city;
24-
25-
public string $avgTemperature;
26-
27-
#[Skip()]
28-
public ?string $bestNeighbourhood = null;
29-
}
30-
313
function camelCaseToSnakeCase(string $string): string
324
{
335
return strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $string));
@@ -64,17 +36,4 @@ function deserialize(string $data, string $className): object
6436
}
6537

6638
return $object;
67-
}
68-
69-
$object = deserialize(
70-
json_encode([
71-
'country' => 'Austria',
72-
'town' => 'Vienna',
73-
'avg_temperature' => '13',
74-
'best_neighbourhood' => 'Penha de França'
75-
]),
76-
CityBreak::class
77-
);
78-
79-
var_dump($object);
80-
39+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
3+
require_once __DIR__ . '/deserialize.php';
4+
require_once __DIR__ . '/attributes.php';

exercises/the-attributes-of-success/problem/problem.md

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
#[Attribute(Attribute::TARGET_CLASS)]
4+
class Deserialize {
5+
6+
}
7+
8+
#[Attribute(Attribute::TARGET_PROPERTY)]
9+
class Map {
10+
public function __construct(public string $mapFrom)
11+
{
12+
}
13+
}
14+
15+
#[Attribute(Attribute::TARGET_PROPERTY)]
16+
class Skip {
17+
18+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
function camelCaseToSnakeCase(string $string): string
4+
{
5+
return strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $string));
6+
}
7+
8+
function deserialize(string $data, string $className): object
9+
{
10+
$reflectionClass = new \ReflectionClass($className);
11+
$attrs = $reflectionClass->getAttributes(Deserialize::class);
12+
13+
if (empty($attrs)) {
14+
throw new \RuntimeException('Class cannot be deserialized');
15+
}
16+
17+
$attrs[0]->newInstance();
18+
19+
$object = new $className;
20+
21+
$data = json_decode($data, true);
22+
23+
foreach ($reflectionClass->getProperties() as $property) {
24+
if ($map = $property->getAttributes(Map::class)) {
25+
$key = $map[0]->newInstance()->mapFrom;
26+
27+
if (isset($data[$key])) {
28+
$object->{$property->getName()} = $data[$key];
29+
}
30+
31+
} elseif ($skip = $property->getAttributes(Skip::class)) {
32+
continue;
33+
} elseif (isset($data[camelCaseToSnakeCase($property->getName())])) {
34+
$object->{$property->getName()} = $data[camelCaseToSnakeCase($property->getName())];
35+
}
36+
}
37+
38+
return $object;
39+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
require_once __DIR__ . '/deserialize.php';
4+
require_once __DIR__ . '/attributes.php';
5+
6+
#[Deserialize]
7+
class Review {
8+
public string $comment;
9+
10+
#[Map('rating')]
11+
public string $starRating;
12+
13+
public string $date;
14+
15+
#[Skip()]
16+
public ?string $reviewer = null;
17+
}
18+
19+
$object = deserialize($argv[1], Review::class);
20+
21+
var_dump($object);
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 Faker\Generator as FakerGenerator;
6+
use PhpParser\Node;
7+
use PhpParser\Node\Stmt;
8+
use PhpParser\Node\Stmt\Class_;
9+
use PhpParser\NodeFinder;
10+
use PhpParser\Parser;
11+
use PhpSchool\PhpWorkshop\Check\FileComparisonCheck;
12+
use PhpSchool\PhpWorkshop\Check\FunctionRequirementsCheck;
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\ProvidesInitialCode;
18+
use PhpSchool\PhpWorkshop\ExerciseCheck\FileComparisonExerciseCheck;
19+
use PhpSchool\PhpWorkshop\ExerciseCheck\FunctionRequirementsExerciseCheck;
20+
use PhpSchool\PhpWorkshop\ExerciseCheck\SelfCheck;
21+
use PhpSchool\PhpWorkshop\ExerciseDispatcher;
22+
use PhpSchool\PhpWorkshop\Input\Input;
23+
use PhpSchool\PhpWorkshop\Result\Failure;
24+
use PhpSchool\PhpWorkshop\Result\ResultInterface;
25+
use PhpSchool\PhpWorkshop\Result\Success;
26+
use PhpSchool\PhpWorkshop\Solution\DirectorySolution;
27+
use PhpSchool\PhpWorkshop\Solution\SolutionInterface;
28+
29+
class TheAttributesOfSuccess extends AbstractExercise implements
30+
ExerciseInterface,
31+
ProvidesInitialCode,
32+
CliExercise,
33+
FunctionRequirementsExerciseCheck,
34+
FileComparisonExerciseCheck,
35+
SelfCheck
36+
{
37+
public function __construct(private Parser $parser, private FakerGenerator $faker)
38+
{
39+
}
40+
41+
public function getName(): string
42+
{
43+
return 'The Attributes of Success';
44+
}
45+
46+
public function getDescription(): string
47+
{
48+
return 'PHP 8\'s Attributes';
49+
}
50+
51+
public function configure(ExerciseDispatcher $dispatcher): void
52+
{
53+
$dispatcher->requireCheck(FileComparisonCheck::class);
54+
$dispatcher->requireCheck(FunctionRequirementsCheck::class);
55+
}
56+
57+
public function getInitialCode(): SolutionInterface
58+
{
59+
return DirectorySolution::fromDirectory(
60+
__DIR__ . '/../../exercises/the-attributes-of-success/initial',
61+
entryPoint: 'the-attributes-of-success.php'
62+
);
63+
}
64+
65+
public function getSolution(): SolutionInterface
66+
{
67+
return DirectorySolution::fromDirectory(
68+
realpath(__DIR__ . '/../../exercises/the-attributes-of-success/solution'),
69+
);
70+
}
71+
72+
public function getType(): ExerciseType
73+
{
74+
return ExerciseType::CLI();
75+
}
76+
77+
public function getArgs(): array
78+
{
79+
return [
80+
json_encode(
81+
[
82+
'comment' => $this->faker->sentence(4),
83+
'rating' => $this->faker->numberBetween(0, 5),
84+
'reviewer' => $this->faker->userName(),
85+
'date' => $this->faker->date('d-m-Y')
86+
],
87+
JSON_THROW_ON_ERROR
88+
)
89+
];
90+
}
91+
92+
public function check(Input $input): ResultInterface
93+
{
94+
/** @var array<Stmt> $statements */
95+
$statements = $this->parser->parse((string) file_get_contents($input->getRequiredArgument('program')));
96+
97+
/** @var Class_|null $classStmt */
98+
$classStmt = (new NodeFinder())->findFirst($statements, function (Node $node) {
99+
return $node instanceof Class_ && $node->name && $node->name->name === 'Review';
100+
});
101+
102+
//not even sure we need this the var_dump will cover it
103+
if ($classStmt === null) {
104+
return new Failure($this->getName(), 'A class named Review was not found');
105+
}
106+
107+
return new Success($this->getName());
108+
}
109+
110+
public function getRequiredFunctions(): array
111+
{
112+
return ['deserialize'];
113+
}
114+
115+
public function getBannedFunctions(): array
116+
{
117+
return [];
118+
}
119+
120+
public function getFilesToCompare(): array
121+
{
122+
return [
123+
'attributes.php',
124+
'deserialize.php'
125+
];
126+
}
127+
}

0 commit comments

Comments
 (0)