Skip to content

Commit 409365a

Browse files
committed
Merge pull request #111 from php-school/hotfix/namespace-save-data
Namespace save data
2 parents 484a39a + 87a3b9b commit 409365a

File tree

4 files changed

+316
-65
lines changed

4 files changed

+316
-65
lines changed

app/config.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,12 @@
231231
$cliRenderer = (new MarkdownCliRendererFactory)->__invoke($c);
232232
return new MarkdownRenderer($docParser, $cliRenderer);
233233
}),
234-
UserStateSerializer::class => factory(function () {
235-
return new UserStateSerializer(sprintf('%s/.phpschool.json', getenv('HOME')));
234+
UserStateSerializer::class => factory(function (ContainerInterface $c) {
235+
return new UserStateSerializer(
236+
getenv('HOME'),
237+
$c->get('workshopTitle'),
238+
$c->get(ExerciseRepository::class)
239+
);
236240
}),
237241
UserState::class => factory(function (ContainerInterface $c) {
238242
return $c->get(UserStateSerializer::class)->deSerialize();

src/Application.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ final class Application
5959
*/
6060
public function __construct($workshopTitle, $diConfigFile)
6161
{
62+
Assertion::string($workshopTitle);
6263
Assertion::file($diConfigFile);
6364

6465
$this->workshopTitle = $workshopTitle;

src/UserStateSerializer.php

Lines changed: 128 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,38 @@ class UserStateSerializer
1515
private $path;
1616

1717
/**
18-
* @param string $path
18+
* @var string
1919
*/
20-
public function __construct($path)
21-
{
22-
$this->path = $path;
20+
private $workshopName;
2321

24-
if (file_exists($path)) {
25-
return;
26-
}
22+
/**
23+
* @var string
24+
*/
25+
const LEGACY_SAVE_FILE = '.phpschool.json';
26+
27+
/**
28+
* @var string
29+
*/
30+
const SAVE_FILE = '.phpschool-save.json';
2731

28-
if (!file_exists(dirname($path))) {
29-
mkdir(dirname($path), 0777, true);
32+
/**
33+
* @var ExerciseRepository
34+
*/
35+
private $exerciseRepository;
36+
37+
/**
38+
* @param string $saveFileDirectory
39+
* @param string $workshopName
40+
* @param ExerciseRepository $exerciseRepository
41+
*/
42+
public function __construct($saveFileDirectory, $workshopName, ExerciseRepository $exerciseRepository)
43+
{
44+
$this->workshopName = $workshopName;
45+
$this->path = $saveFileDirectory;
46+
$this->exerciseRepository = $exerciseRepository;
47+
48+
if (!file_exists($this->path)) {
49+
mkdir($this->path, 0777, true);
3050
}
3151
}
3252

@@ -37,60 +57,65 @@ public function __construct($path)
3757
*/
3858
public function serialize(UserState $state)
3959
{
40-
return file_put_contents($this->path, json_encode([
60+
$saveFile = sprintf('%s/%s', $this->path, static::SAVE_FILE);
61+
62+
$data = file_exists($saveFile)
63+
? $this->readJson($saveFile)
64+
: [];
65+
66+
$data[$this->workshopName] = [
4167
'completed_exercises' => $state->getCompletedExercises(),
4268
'current_exercise' => $state->getCurrentExercise(),
43-
]));
69+
];
70+
71+
return file_put_contents($saveFile, json_encode($data));
4472
}
4573

4674
/**
4775
* @return UserState
4876
*/
4977
public function deSerialize()
5078
{
51-
if (!file_exists($this->path)) {
52-
return new UserState();
53-
}
79+
$legacySaveFile = sprintf('%s/%s', $this->path, static::LEGACY_SAVE_FILE);
80+
if (file_exists($legacySaveFile)) {
81+
$userState = $this->migrateData($legacySaveFile);
5482

55-
$data = file_get_contents($this->path);
83+
if ($userState instanceof UserState) {
84+
return $userState;
85+
}
86+
}
5687

57-
if (trim($data) === "") {
88+
$json = $this->readJson(sprintf('%s/%s', $this->path, static::SAVE_FILE));
89+
if (null === $json) {
5890
$this->wipeFile();
5991
return new UserState();
6092
}
6193

62-
$json = @json_decode($data, true);
63-
64-
if (null === $json && JSON_ERROR_NONE !== json_last_error()) {
65-
$this->wipeFile();
94+
if (!isset($json[$this->workshopName])) {
6695
return new UserState();
6796
}
6897

98+
$json = $json[$this->workshopName];
6999
if (!array_key_exists('completed_exercises', $json)) {
70-
$this->wipeFile();
71100
return new UserState();
72101
}
73102

74-
if (!is_array($json['completed_exercises'])) {
75-
$this->wipeFile();
103+
if (!array_key_exists('current_exercise', $json)) {
76104
return new UserState();
77105
}
78106

79-
foreach ($json['completed_exercises'] as $exercise) {
80-
if (!is_string($exercise)) {
81-
$this->wipeFile();
82-
return new UserState();
83-
}
107+
if (!is_array($json['completed_exercises'])) {
108+
$json['completed_exercises'] = [];
84109
}
85110

86-
if (!array_key_exists('current_exercise', $json)) {
87-
$this->wipeFile();
88-
return new UserState();
111+
foreach ($json['completed_exercises'] as $i => $exercise) {
112+
if (!is_string($exercise)) {
113+
unset($json['completed_exercises'][$i]);
114+
}
89115
}
90116

91117
if (null !== $json['current_exercise'] && !is_string($json['current_exercise'])) {
92-
$this->wipeFile();
93-
return new UserState();
118+
$json['current_exercise'] = null;
94119
}
95120

96121
return new UserState(
@@ -99,11 +124,80 @@ public function deSerialize()
99124
);
100125
}
101126

127+
/**
128+
* On early versions of the workshop the save data was not namespaced
129+
* and therefore it was impossible to have data for more than one workshop
130+
* at the same time. Therefore we must try to migrate that data in to the new namespaced
131+
* format in order to preserve users save data.
132+
*
133+
* We can only migrate data when using the workshop the data was originally saved from
134+
*
135+
* @param string $legacySaveFile
136+
* @return null|UserState
137+
*/
138+
private function migrateData($legacySaveFile)
139+
{
140+
$data = $this->readJson($legacySaveFile);
141+
142+
if (null === $data) {
143+
unlink($legacySaveFile);
144+
return null;
145+
}
146+
147+
//lets check if the data is in the old format
148+
if (!isset($data['completed_exercises']) || !isset($data['current_exercise'])) {
149+
unlink($legacySaveFile);
150+
return null;
151+
}
152+
153+
$completedExercises = $data['completed_exercises'];
154+
$availableExercises = $this->exerciseRepository->getAllNames();
155+
156+
//check to see if this old data represents THIS workshop
157+
//if not we bail
158+
foreach ($completedExercises as $completedExercise) {
159+
if (!in_array($completedExercise, $availableExercises)) {
160+
return null;
161+
}
162+
}
163+
164+
$userState = new UserState($data['completed_exercises'], $data['current_exercise']);
165+
$this->serialize($userState);
166+
167+
unlink($legacySaveFile);
168+
return $userState;
169+
}
170+
171+
/**
172+
* @param string $filePath
173+
* @return array|null
174+
*/
175+
private function readJson($filePath)
176+
{
177+
if (!file_exists($filePath)) {
178+
return null;
179+
}
180+
181+
$data = file_get_contents($filePath);
182+
183+
if (trim($data) === "") {
184+
return null;
185+
}
186+
187+
$data = @json_decode($data, true);
188+
189+
if (null === $data && JSON_ERROR_NONE !== json_last_error()) {
190+
return null;
191+
}
192+
193+
return $data;
194+
}
195+
102196
/**
103197
* Remove the file
104198
*/
105199
private function wipeFile()
106200
{
107-
unlink($this->path);
201+
@unlink($this->path);
108202
}
109203
}

0 commit comments

Comments
 (0)