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; |
||
0 ignored issues
–
show
|
|||
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
It seems like
$timestamp defined by $this->getTimestamp() on line 245 can also be of type false ; however, Battis\HierarchicalSimpleCache::getCache() 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...
|
|||
250 | $this->averages = $this->getCache()->getCache("$timestamp-averages"); |
||
0 ignored issues
–
show
It seems like
$this->getCache()->getCa...{$timestamp}-averages") of type boolean is incompatible with the declared type array of property $averages .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..
Loading history...
|
|||
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 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.