Skip to content

Commit d3fd88f

Browse files
davidszkibamona-shakiba
authored andcommitted
GH-791 Better use of result pattern
1 parent f34aca8 commit d3fd88f

File tree

4 files changed

+168
-86
lines changed

4 files changed

+168
-86
lines changed

classes/local/none.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
/**
18+
* Сlass status.
19+
*
20+
* @package local_catquiz
21+
* @copyright 2023 Wunderbyte GmbH <[email protected]>
22+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23+
*/
24+
25+
namespace local_catquiz\local;
26+
27+
use Exception;
28+
use local_catquiz\local\status;
29+
30+
/**
31+
* Provides methods to obtain results.
32+
*
33+
*
34+
* @package local_catquiz
35+
* @copyright 2023 Wunderbyte GmbH <[email protected]>
36+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37+
*/
38+
class none extends result {
39+
public function and_then(callable $op): result {
40+
return $this;
41+
}
42+
43+
public function or_else(callable $op): result {
44+
return $op($this);
45+
}
46+
47+
public function expect() {
48+
throw new Exception($this->geterrormessage());
49+
}
50+
}

classes/local/result.php

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
namespace local_catquiz\local;
2626

27+
use Exception;
2728
use local_catquiz\local\status;
2829

2930
/**
@@ -34,8 +35,7 @@
3435
* @copyright 2023 Wunderbyte GmbH <[email protected]>
3536
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3637
*/
37-
class result {
38-
38+
abstract class result {
3939
/**
4040
* @var string status
4141
*/
@@ -63,25 +63,34 @@ public function __construct($value = null, string $status = status::OK) {
6363
* @param string $status
6464
* @param mixed|null $value
6565
*
66-
* @return result
66+
* @return none
6767
*
6868
*/
6969
public static function err(string $status = status::ERROR_GENERAL, $value = null) {
70-
return new result($value, $status);
70+
return new none($value, $status);
7171
}
7272

7373
/**
7474
* Returns OK result.
7575
*
7676
* @param mixed|null $value
7777
*
78-
* @return result
78+
* @return some
7979
*
8080
*/
8181
public static function ok($value = null) {
82-
return new result($value, status::OK);
82+
return new some($value, status::OK);
8383
}
8484

85+
abstract public function and_then(callable $op): result;
86+
87+
abstract public function or_else(callable $op): result;
88+
89+
/**
90+
* @throws Exception
91+
*/
92+
abstract public function expect();
93+
8594
/**
8695
* Returns status.
8796
*

classes/local/some.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
/**
18+
* Сlass status.
19+
*
20+
* @package local_catquiz
21+
* @copyright 2023 Wunderbyte GmbH <[email protected]>
22+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23+
*/
24+
25+
namespace local_catquiz\local;
26+
27+
use local_catquiz\local\status;
28+
29+
/**
30+
* Provides methods to obtain results.
31+
*
32+
*
33+
* @package local_catquiz
34+
* @copyright 2023 Wunderbyte GmbH <[email protected]>
35+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36+
*/
37+
class some extends result {
38+
39+
public function and_then(callable $op): result {
40+
return $op($this);
41+
}
42+
43+
public function or_else(callable $op): result {
44+
return $this;
45+
}
46+
47+
public function expect() {
48+
return $this;
49+
}
50+
}

classes/teststrategy/strategy.php

Lines changed: 53 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,15 @@ abstract class strategy {
8787
*/
8888
public int $catcontextid;
8989

90-
/**
91-
* @var array<preselect_task>
92-
*/
93-
public array $scoremodifiers;
94-
9590
/**
9691
* @var progress
9792
*/
9893
protected progress $progress;
9994

95+
private cache $cache;
96+
97+
private $result;
98+
10099
/**
101100
* These data provide the context for the selection of the next question.
102101
*
@@ -115,8 +114,7 @@ abstract class strategy {
115114
public function __construct() {
116115
global $CFG;
117116
require_once($CFG->dirroot . '/local/catquiz/lib.php');
118-
119-
$this->scoremodifiers = info::get_score_modifiers();
117+
$this->cache = cache::make('local_catquiz', 'adaptivequizattempt');
120118
}
121119

122120
/**
@@ -160,15 +158,14 @@ public function get_description(): string {
160158
*/
161159
public function return_next_testitem(array $context) {
162160
$this->context = $context;
163-
$cache = cache::make('local_catquiz', 'adaptivequizattempt');
164161
$maxtime = $context['progress']->get_starttime() + $context['max_attempttime_in_sec'];
165162
if (time() > $maxtime) {
166163
// The 'endtime' holds the last timepoint that is relevant for the
167164
// result. So if a student tries to answer a question after the
168165
// time limit, this value is set to the time limit (starttime + max
169166
// attempt time).
170-
$cache->set('endtime', $maxtime);
171-
$cache->set('catquizerror', status::EXCEEDED_MAX_ATTEMPT_TIME);
167+
$this->cache->set('endtime', $maxtime);
168+
$this->cache->set('catquizerror', status::EXCEEDED_MAX_ATTEMPT_TIME);
172169
return result::err(status::EXCEEDED_MAX_ATTEMPT_TIME);
173170
}
174171

@@ -193,96 +190,63 @@ public function return_next_testitem(array $context) {
193190
}
194191

195192
// Core methods called in every strategy.
196-
$res = $this->update_personability();
197-
if ($res->iserr()) {
198-
$cache->set('endtime', time());
199-
$cache->set('catquizerror', $res->get_status());
200-
return $res;
201-
}
202-
$context = $res->unwrap();
193+
$res = $this->update_personability()
194+
->and_then(fn () => $this->first_question_selector())
195+
->or_else(fn ($res) => $this->after_error($res));
203196

204-
$res = $this->first_question_selector();
205-
if ($res->iserr()) {
206-
$cache->set('endtime', time());
207-
$cache->set('catquizerror', $res->get_status());
208-
return $res;
209-
}
210197
$val = $res->unwrap();
211198
// If the result contains a single object, this is the question to be returned.
212199
// Othewise, it contains the updated $context array with the ability and standarderror set in such a way, that the
213200
// teststrategy will return the correct question (e.g. the question corresponding to mean ability of all students).
214201
if (is_object($val)) {
215202
return $res;
216203
}
217-
$this->context = $val;
218204

219-
$res = $this->add_scale_standarderror();
220-
if ($res->iserr()) {
221-
$cache->set('endtime', time());
222-
$cache->set('catquizerror', $res->get_status());
223-
return $res;
224-
}
225-
$this->context = $res->unwrap();
226-
227-
$res = $this->maximumquestionscheck();
228-
if ($res->iserr()) {
229-
$cache->set('endtime', time());
230-
$cache->set('catquizerror', $res->get_status());
231-
return $res;
232-
}
233-
234-
$res = $this->removeplayedquestions();
235-
if ($res->iserr()) {
236-
$cache->set('endtime', time());
237-
$cache->set('catquizerror', $res->get_status());
238-
return $res;
239-
}
240-
$this->context = $res->unwrap();
241-
242-
$res = $this->noremainingquestions();
243-
if ($res->iserr()) {
244-
$cache->set('endtime', time());
245-
$cache->set('catquizerror', $res->get_status());
246-
return $res;
205+
try {
206+
$this->add_scale_standarderror()
207+
->and_then(fn () => $this->maximumquestionscheck())
208+
->and_then(fn () => $this->removeplayedquestions())
209+
->and_then(fn () => $this->noremainingquestions())
210+
->and_then(fn () => $this->fisherinformation())
211+
->or_else(fn($res) => $this->after_error($res))
212+
->expect();
213+
} catch (Exception $e) {
214+
return $this->result;
247215
}
248216

249-
$this->context = $this->fisherinformation()->unwrap();
250-
251217
$res = $this->maybereturnpilot();
252218
$val = $res->unwrap();
253219
// If the value is an object, it is the pilot question that should be returned.
254220
if (is_object($val)) {
255221
return $res;
256222
}
257-
$this->context = $val;
258-
259-
$this->context = $this->remove_uncalculated()->unwrap();
260223

261-
$this->context = $this->mayberemovescale()->unwrap();
262-
263-
$this->context = $this->last_time_played_penalty()->unwrap();
264-
265-
$res = $this->filterbystandarderror();
266-
if ($res->iserr()) {
267-
$cache->set('endtime', time());
268-
$cache->set('catquizerror', $res->get_status());
269-
return $res;
224+
try {
225+
$this->remove_uncalculated()
226+
->and_then(fn() => $this->mayberemovescale())
227+
->and_then(fn() => $this->last_time_played_penalty())
228+
->and_then(fn() => $this->filterbystandarderror())
229+
->or_else(fn($res) => $this->after_error($res))
230+
->expect();
231+
} catch (Exception $e) {
232+
return $this->result;
270233
}
271234

272-
$this->context = $this->filterbytestinfo()->unwrap();
273-
274-
$this->context = $this->filterbyquestionsperscale()->unwrap();
275-
276-
$result = $this->select_question();
277-
278-
$this->progress->save();
279-
280-
$this->update_attemptfeedback($context);
281-
282-
if ($result->iserr()) {
283-
$cache->set('endtime', time());
284-
$cache->set('catquizerror', $result->get_status());
285-
return $result;
235+
try {
236+
$result = $this->filterbytestinfo()
237+
->and_then(fn() => $this->filterbyquestionsperscale())
238+
->and_then(fn() => $this->select_question())
239+
->and_then(
240+
function ($res) {
241+
$this->progress->save();
242+
$this->update_attemptfeedback($this->context);
243+
return $res;
244+
}
245+
)
246+
->or_else(fn ($res) => $this->after_error($res))
247+
->expect();
248+
} catch (Exception $e) {
249+
return $this->result;
286250
}
287251

288252
$selectedquestion = $result->unwrap();
@@ -304,6 +268,15 @@ public function return_next_testitem(array $context) {
304268
return result::ok($selectedquestion);
305269
}
306270

271+
private function after_error($result) {
272+
$this->progress->save();
273+
$this->update_attemptfeedback($this->context);
274+
$this->cache->set('endtime', time());
275+
$this->cache->set('catquizerror', $result->get_status());
276+
$this->result = $result;
277+
return $result;
278+
}
279+
307280
/**
308281
* If true, the check page reload is called before updating the ability.
309282
*

0 commit comments

Comments
 (0)