These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** Snapshot class */ |
||
3 | |||
4 | namespace smtech\GradingAnalytics\Snapshots; |
||
5 | |||
6 | /** |
||
7 | * A snapshot of course statistics relevant to a particular domain |
||
8 | * |
||
9 | * @author Seth Battis <[email protected]> |
||
10 | */ |
||
11 | class Snapshot extends CacheableDatabase |
||
12 | { |
||
13 | const NOT_ASSOCIATED_WITH_COURSE = '-'; |
||
14 | |||
15 | /** |
||
16 | * History of the course specified by `$courseOrDepartmentId` in constructor |
||
17 | * @var History |
||
18 | */ |
||
19 | protected $history; |
||
20 | |||
21 | /** |
||
22 | * Department ID given as `$courseOrDepartmentId` in constructor |
||
23 | * @var string|integer|false |
||
24 | */ |
||
25 | protected $departmentId = false; |
||
26 | |||
27 | /** |
||
28 | * The domain of this snapshot |
||
29 | * @var Domain |
||
30 | */ |
||
31 | protected $domain; |
||
32 | |||
33 | /** |
||
34 | * Date timestamp of this snapshot |
||
35 | * @var string|false |
||
36 | */ |
||
37 | protected $timestamp = false; |
||
38 | |||
39 | /** |
||
40 | * Course statistics records that make up the snapshot |
||
41 | * @var array |
||
42 | */ |
||
43 | protected static $data; |
||
44 | |||
45 | /** |
||
46 | * Averages calculated from the data |
||
47 | * @var array |
||
48 | */ |
||
49 | protected $averages; |
||
50 | |||
51 | /** |
||
52 | * Teacher filter (a numerica Canvas user ID) for restricted views of data |
||
53 | * @var string|integer|boolean |
||
54 | */ |
||
55 | public static $teacherFilter; |
||
56 | |||
57 | /** |
||
58 | * Construct a Snapshot object |
||
59 | * |
||
60 | * Note that the snapshots are queried and/or cached from the database |
||
61 | * "just in time", and not during instantiation. |
||
62 | * |
||
63 | * If the snapshot is constructed relative to a course ID, the domain will |
||
64 | * being snapshotted will will be snapshotted at the time of that course's |
||
65 | * most recent statistic collection (so, if it's June 20, and the last |
||
66 | * statistic collected for the course was May 20, the department snapshot |
||
67 | * will be for May 20, even if more recent snapshots are available within |
||
68 | * the department). |
||
69 | * |
||
70 | * @param \mysqli|\smtech\ReflexiveCanvasLTI\Toolbox\CacheableDatabase |
||
71 | * $databaseProvider An object containing a mysqli database access |
||
72 | * object |
||
73 | * @param Domain $domain The domain of the snapshot |
||
74 | * @param string|integer $courseOrDepartmentId A numeric Canvas course or |
||
75 | * ccount ID (course is is assumed unless explicitly specified by |
||
76 | * `$isCourseId`) |
||
77 | * @param boolean $isCourseId (Optional, defaults to `TRUE`) Whether |
||
78 | * `$courseOrDepartmentId` is course ID (`TRUE`) |
||
79 | * or an account ID (`FALSE`) |
||
80 | */ |
||
81 | public function __construct($databaseProvider, Domain $domain, $courseOrDepartmentId, $isCourseId = true) |
||
82 | { |
||
83 | parent::__construct($databaseProvider); |
||
84 | |||
85 | $this->domain = $domain; |
||
86 | |||
87 | if ($isCourseId) { |
||
88 | $this->history = new History($this, $courseOrDepartmentId); |
||
89 | } elseif (is_numeric($courseOrDepartmentId)) { |
||
90 | $this->departmentId = $courseOrDepartmentId; |
||
91 | } |
||
92 | } |
||
93 | |||
94 | /** |
||
95 | * Get the numeric Canvas course ID relevant to this snapshot |
||
96 | * |
||
97 | * There may be no course relevant to this snapshot if it was created from |
||
98 | * a department ID. |
||
99 | * |
||
100 | * @return string|integer Returns `Snapshot::NOT_ASSOCIATED_WITH_COURSE` if |
||
101 | * no course is associated with the snapshot. |
||
102 | */ |
||
103 | public function getCourseId() |
||
104 | { |
||
105 | if (!empty($this->history)) { |
||
106 | return $this->history->getCourseId(); |
||
107 | } |
||
108 | return self::NOT_ASSOCIATED_WITH_COURSE; |
||
109 | } |
||
110 | |||
111 | /** |
||
112 | * Get the numeric Canvas account ID relevant to this snapshot |
||
113 | * |
||
114 | * @return string|integer|false Will return `FALSE` if the snapshot is |
||
115 | * associated with a course for which |
||
116 | * there are not yet any statistics |
||
117 | * available. |
||
118 | */ |
||
119 | public function getDepartmentId() |
||
120 | { |
||
121 | if ($this->departmentId === false) { |
||
122 | $this->departmentId = $this->history->getDepartmentId(); |
||
123 | } |
||
124 | return $this->departmentId; |
||
125 | } |
||
126 | |||
127 | /** |
||
128 | * Get the domain of the snapshot |
||
129 | * |
||
130 | * @return Domain |
||
131 | */ |
||
132 | public function getDomain() |
||
133 | { |
||
134 | return $this->domain; |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Get the date of the snapshot |
||
139 | * |
||
140 | * @see Snapshot::__construct() `Snapshot::__construct()` for a fuller |
||
141 | * discussion of timestamp selection |
||
142 | * |
||
143 | * @return string|false Returns the date in `YYYY-MM-DD` format or `FALSE` |
||
144 | * if a timestamp could not be calculated (if no |
||
145 | * statistics are available for the current |
||
146 | * domain/course combination, for example) |
||
147 | */ |
||
148 | public function getTimestamp() |
||
149 | { |
||
150 | if ($this->timestamp === false) { |
||
151 | if (!empty($this->history)) { |
||
152 | $this->timestamp = $this->history->getCurrentTimestamp(); |
||
153 | } else { |
||
154 | if ($response = $this->getMySql()->query(" |
||
155 | SELECT * FROM `course_statistics` |
||
156 | WHERE |
||
157 | `course[account_id]` = '" . $this->getDepartmentId() . "' |
||
158 | ORDER BY |
||
159 | `timestamp` DESC |
||
160 | LIMIT 1 |
||
161 | ")) { |
||
162 | if ($row = $response->fetch_assoc()) { |
||
163 | $this->timestamp = substr($row['timestamp'], 0, 10); |
||
164 | } |
||
165 | } |
||
166 | } |
||
167 | } |
||
168 | return $this->timestamp; |
||
169 | } |
||
170 | |||
171 | /** |
||
172 | * Get a particular average calculated across the snapshot |
||
173 | * |
||
174 | * @see Average `Average` for available averages |
||
175 | * |
||
176 | * @param Average $average |
||
177 | * @return double|false Returns `FALSE` if an average could not be computed |
||
178 | * (e.g. if there are no stastics from which to |
||
179 | * compute it) |
||
180 | */ |
||
181 | public function getAverage(Average $average) |
||
182 | { |
||
183 | if ($this->cacheSnapshot()) { |
||
184 | return $this->averages[$average->getValue()]; |
||
185 | } |
||
186 | return false; |
||
187 | } |
||
188 | |||
189 | /** |
||
190 | * Get the snapshotted course statistics data |
||
191 | * |
||
192 | * @link https://smtech.github.io/grading-analytics/definitions.html Online |
||
193 | * documentation of course statistic fields |
||
194 | * |
||
195 | * @param string|integer|boolean $teacherFilter (Optional, defaults to |
||
196 | * `FALSE`) An optional filter |
||
197 | * to limit the snapshot data |
||
198 | * to courses taught by a |
||
199 | * particular numerica Canvas |
||
200 | * user ID |
||
201 | * @return array|false One row per course statistic collected daily, as |
||
202 | * described in the documentation, or `FALSE` if no |
||
203 | * statistics are available |
||
204 | */ |
||
205 | public function getSnapshot($teacherFilter = false) |
||
206 | { |
||
207 | if ($this->domain == Domain::COURSE()) { |
||
208 | $snapshot = $this->history->getHistory(); |
||
209 | if (is_array($snapshot) && count($snapshot) > 0) { |
||
210 | return $snapshot[0]; |
||
211 | } |
||
212 | } elseif ($this->cacheSnapshot()) { |
||
213 | $d = $this->getDomain()->getValue(); |
||
214 | if ($teacherFilter) { |
||
215 | self::$teacherFilter = $teacherFilter; |
||
216 | return array_filter( |
||
217 | static::$data[$this->getCourseId()][$d][$this->getTimestamp()], |
||
218 | function ($elt) { |
||
219 | return array_search( |
||
220 | self::$teacherFilter, |
||
221 | unserialize($elt['teacher[id]s']) |
||
222 | ) !== false; |
||
223 | } |
||
224 | ); |
||
225 | } |
||
226 | return static::$data[$this->getCourseId()][$d][$this->getTimestamp()]; |
||
227 | } |
||
228 | return false; |
||
229 | } |
||
230 | |||
231 | /** |
||
232 | * Trigger a caching of snapshotted course statistics, if not already cached |
||
233 | * |
||
234 | * @return boolean `TRUE` if there is a non-empty cache of course |
||
235 | * statistics to work with, `FALSE` otherwise |
||
236 | */ |
||
237 | public function cacheSnapshot() |
||
238 | { |
||
239 | $domain = $this->getDomain(); |
||
240 | if ($domain == Domain::COURSE()) { |
||
241 | return $this->history->cacheHistory(); |
||
242 | } else { |
||
243 | $courseId = $this->getCourseId(); |
||
244 | $d = $domain->getValue(); |
||
245 | $timestamp = $this->getTimestamp(); |
||
246 | if (empty(static::$data[$courseId][$d][$timestamp])) { |
||
247 | $this->getCache()->pushKey($courseId); |
||
248 | $this->getCache()->pushKey(($domain == Domain::DEPARTMENT() ? $this->getDepartmentId() : 'school')); |
||
249 | static::$data[$courseId][$d][$timestamp] = $this->getCache()->getCache($timestamp); |
||
0 ignored issues
–
show
|
|||
250 | $this->averages = $this->getCache()->getCache("$timestamp-averages"); |
||
251 | if (empty(static::$data[$courseId][$d][$timestamp])) { |
||
252 | if ($response = $this->getMySql()->query(" |
||
253 | SELECT * FROM `course_statistics` |
||
254 | WHERE |
||
255 | " . ($domain == Domain::DEPARTMENT() ? |
||
256 | "`course[account_id]` = '" . $this->getDepartmentId() . "' AND" : |
||
257 | '' |
||
258 | ) . " |
||
259 | `timestamp` LIKE '$timestamp%' |
||
260 | GROUP BY |
||
261 | `course[id]` |
||
262 | ORDER BY |
||
263 | `timestamp` DESC |
||
264 | ")) { |
||
265 | $total = [ |
||
266 | Average::TURN_AROUND => 0, |
||
267 | Average::ASSIGNMENT_COUNT => 0 |
||
268 | ]; |
||
269 | $divisor = [ |
||
270 | Average::TURN_AROUND => 0, |
||
271 | Average::ASSIGNMENT_COUNT => $response->num_rows |
||
272 | ]; |
||
273 | |||
274 | while ($row = $response->fetch_assoc()) { |
||
275 | static::$data[$courseId][$d][$timestamp][] = $row; |
||
276 | |||
277 | $total[Average::TURN_AROUND] += |
||
278 | $row['average_grading_turn_around'] * |
||
279 | $row['student_count'] * |
||
280 | $row['graded_assignment_count']; |
||
281 | $divisor[Average::TURN_AROUND] += |
||
282 | $row['student_count'] * |
||
283 | $row['graded_assignment_count']; |
||
284 | |||
285 | $total[Average::ASSIGNMENT_COUNT] += |
||
286 | $row['assignments_due_count'] + |
||
287 | $row['dateless_assignment_count']; |
||
288 | } |
||
289 | |||
290 | foreach (Average::values() as $avg) { |
||
291 | $i = $avg->getValue(); |
||
292 | $this->averages[$i] = ($divisor[$i] !== 0 ? |
||
293 | $total[$i] / $divisor[$i] : |
||
294 | 0 |
||
295 | ); |
||
296 | } |
||
297 | |||
298 | $this->getCache()->setCache($timestamp, static::$data[$courseId][$d][$timestamp]); |
||
0 ignored issues
–
show
It seems like
$timestamp defined by $this->getTimestamp() on line 245 can also be of type false ; however, Battis\HierarchicalSimpleCache::setCache() does only seem to accept string , did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new
Loading history...
|
|||
299 | $this->getCache()->setCache("$timestamp-averages", $this->averages); |
||
300 | } |
||
301 | } |
||
302 | } |
||
303 | return is_array(static::$data) && |
||
304 | is_array(static::$data[$courseId]) && |
||
305 | is_array(static::$data[$courseId][$d]) && |
||
306 | is_array(static::$data[$courseId][$d][$timestamp]) && |
||
307 | count(static::$data[$courseId][$d][$timestamp]) > 0; |
||
308 | } |
||
309 | } |
||
310 | } |
||
311 |
This check looks for type mismatches where the missing type is
false
. This is usually indicative of an error condtion.Consider the follow example
This function either returns a new
DateTime
object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returnedfalse
before passing on the value to another function or method that may not be able to handle afalse
.