Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402]

   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   * Extra information generated during the analysis by calculable elements.
  19   *
  20   * @package   core_analytics
  21   * @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_analytics;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * Extra information generated during the analysis by calculable elements.
  31   *
  32   * The main purpose of this request cache is to allow calculable elements to
  33   * store data during their calculations for further use at a later stage efficiently.
  34   *
  35   * @package   core_analytics
  36   * @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
  37   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class calculation_info {
  40  
  41      /**
  42       * @var array
  43       */
  44      private $info = [];
  45  
  46      /**
  47       * @var mixed[]
  48       */
  49      private $samplesinfo = [];
  50  
  51      /**
  52       * Adds info related to the current calculation for later use when generating insights.
  53       *
  54       * Note that the data in $info array is reused across multiple samples, if you want to add data just for this
  55       * sample you can use the sample id as key.
  56       *
  57       * We store two different arrays so objects that appear multiple times for different samples
  58       * appear just once in memory.
  59       *
  60       * @param int    $sampleid  The sample id this data is associated with
  61       * @param array  $info      The data. Indexed by an id unique across the site. E.g. an activity id.
  62       * @return null
  63       */
  64      public function add_shared(int $sampleid, array $info) {
  65  
  66          // We can safely overwrite the existing keys because the provided info is supposed to be unique
  67          // for the indicator.
  68          $this->info = $info + $this->info;
  69  
  70          // We also need to store the association between the info provided and the sample.
  71          $this->samplesinfo[$sampleid] = array_keys($info);
  72      }
  73  
  74      /**
  75       * Stores in MUC the previously added data and it associates it to the provided $calculable.
  76       *
  77       * @param  \core_analytics\calculable                $calculable
  78       * @param  \core_analytics\local\time_splitting\base $timesplitting
  79       * @param  int                                       $rangeindex
  80       * @return null
  81       */
  82      public function save(\core_analytics\calculable $calculable, \core_analytics\local\time_splitting\base $timesplitting,
  83              int $rangeindex) {
  84  
  85          $calculableclass = get_class($calculable);
  86          $cache = \cache::make('core', 'calculablesinfo');
  87  
  88          foreach ($this->info as $key => $value) {
  89              $datakey = self::get_data_key($calculableclass, $key);
  90  
  91              // We do not overwrite existing data.
  92              if (!$cache->has($datakey)) {
  93                  $cache->set($datakey, $value);
  94              }
  95          }
  96  
  97          foreach ($this->samplesinfo as $sampleid => $infokeys) {
  98              $uniquesampleid = $timesplitting->append_rangeindex($sampleid, $rangeindex);
  99              $samplekey = self::get_sample_key($uniquesampleid);
 100  
 101              // Update the cached data adding the new indicator data.
 102              $cacheddata = $cache->get($samplekey) ?: [];
 103              $cacheddata[$calculableclass] = $infokeys;
 104              $cache->set($samplekey, $cacheddata);
 105          }
 106  
 107          // Empty the in-memory arrays now that it is in the cache.
 108          $this->info = [];
 109          $this->samplesinfo = [];
 110      }
 111  
 112      /**
 113       * Pulls the info related to the provided records out from the cache.
 114       *
 115       * Note that this function purges 'calculablesinfo' cache.
 116       *
 117       * @param  \stdClass[] $predictionrecords
 118       * @return array|false
 119       */
 120      public static function pull_info(array $predictionrecords) {
 121  
 122          $cache = \cache::make('core', 'calculablesinfo');
 123  
 124          foreach ($predictionrecords as $uniquesampleid => $predictionrecord) {
 125  
 126              $sampleid = $predictionrecord->sampleid;
 127  
 128              $sampleinfo = $cache->get(self::get_sample_key($uniquesampleid));
 129  
 130              // MUC returns (or should return) copies of the data and we want a single copy of it so
 131              // we store the data here and reference it from each sample. Samples data should not be
 132              // changed afterwards.
 133              $data = [];
 134  
 135              if ($sampleinfo) {
 136                  foreach ($sampleinfo as $calculableclass => $infokeys) {
 137  
 138                      foreach ($infokeys as $infokey) {
 139  
 140                          // We don't need to retrieve data back from MUC if we already have it.
 141                          if (!isset($data[$calculableclass][$infokey])) {
 142                              $datakey = self::get_data_key($calculableclass, $infokey);
 143                              $data[$calculableclass][$infokey] = $cache->get($datakey);
 144                          }
 145  
 146                          $samplesdatakey = $calculableclass . ':extradata';
 147                          $samplesdata[$sampleid][$samplesdatakey][$infokey] = & $data[$calculableclass][$infokey];
 148                      }
 149                  }
 150              }
 151          }
 152  
 153          // Free memory ASAP. We can replace the purge call by a delete_many if we are interested on allowing
 154          // multiple calls to pull_info passing in different $sampleids.
 155          $cache->purge();
 156  
 157          if (empty($samplesdata)) {
 158              return false;
 159          }
 160  
 161          return $samplesdata;
 162      }
 163  
 164      /**
 165       * Gets the key used to store data.
 166       *
 167       * @param  string       $calculableclass
 168       * @param  string|int   $key
 169       * @return string
 170       */
 171      private static function get_data_key(string $calculableclass, $key): string {
 172          return 'data:' . $calculableclass . ':' . $key;
 173      }
 174  
 175      /**
 176       * Gets the key used to store samples.
 177       *
 178       * @param  string $uniquesampleid
 179       * @return string
 180       */
 181      private static function get_sample_key(string $uniquesampleid): string {
 182          return 'sample:' . $uniquesampleid;
 183      }
 184  }