[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/classes/event/ -> manager.php (source)

   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  namespace core\event;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  
  21  /**
  22   * New event manager class.
  23   *
  24   * @package    core
  25   * @since      Moodle 2.6
  26   * @copyright  2013 Petr Skoda {@link http://skodak.org}
  27   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  28   */
  29  
  30  /**
  31   * Class used for event dispatching.
  32   *
  33   * Note: Do NOT use directly in your code, it is intended to be used from
  34   *       base event class only.
  35   */
  36  class manager {
  37      /** @var array buffer of event for dispatching */
  38      protected static $buffer = array();
  39  
  40      /** @var array buffer for events that were not sent to external observers when DB transaction in progress */
  41      protected static $extbuffer = array();
  42  
  43      /** @var bool evert dispatching already in progress - prevents nesting */
  44      protected static $dispatching = false;
  45  
  46      /** @var array cache of all observers */
  47      protected static $allobservers = null;
  48  
  49      /** @var bool should we reload observers after the test? */
  50      protected static $reloadaftertest = false;
  51  
  52      /**
  53       * Trigger new event.
  54       *
  55       * @internal to be used only from \core\event\base::trigger() method.
  56       * @param \core\event\base $event
  57       *
  58       * @throws \coding_Exception if used directly.
  59       */
  60      public static function dispatch(\core\event\base $event) {
  61          if (during_initial_install()) {
  62              return;
  63          }
  64          if (!$event->is_triggered() or $event->is_dispatched()) {
  65              throw new \coding_exception('Illegal event dispatching attempted.');
  66          }
  67  
  68          self::$buffer[] = $event;
  69  
  70          if (self::$dispatching) {
  71              return;
  72          }
  73  
  74          self::$dispatching = true;
  75          self::process_buffers();
  76          self::$dispatching = false;
  77      }
  78  
  79      /**
  80       * Notification from DML layer.
  81       * @internal to be used from DML layer only.
  82       */
  83      public static function database_transaction_commited() {
  84          if (self::$dispatching or empty(self::$extbuffer)) {
  85              return;
  86          }
  87  
  88          self::$dispatching = true;
  89          self::process_buffers();
  90          self::$dispatching = false;
  91      }
  92  
  93      /**
  94       * Notification from DML layer.
  95       * @internal to be used from DML layer only.
  96       */
  97      public static function database_transaction_rolledback() {
  98          self::$extbuffer = array();
  99      }
 100  
 101      protected static function process_buffers() {
 102          global $DB, $CFG;
 103          self::init_all_observers();
 104  
 105          while (self::$buffer or self::$extbuffer) {
 106  
 107              $fromextbuffer = false;
 108              $addedtoextbuffer = false;
 109  
 110              if (self::$extbuffer and !$DB->is_transaction_started()) {
 111                  $fromextbuffer = true;
 112                  $event = reset(self::$extbuffer);
 113                  unset(self::$extbuffer[key(self::$extbuffer)]);
 114  
 115              } else if (self::$buffer) {
 116                  $event = reset(self::$buffer);
 117                  unset(self::$buffer[key(self::$buffer)]);
 118  
 119              } else {
 120                  return;
 121              }
 122  
 123              $observingclasses = self::get_observing_classes($event);
 124              foreach ($observingclasses as $observingclass) {
 125                  if (!isset(self::$allobservers[$observingclass])) {
 126                      continue;
 127                  }
 128                  foreach (self::$allobservers[$observingclass] as $observer) {
 129                      if ($observer->internal) {
 130                          if ($fromextbuffer) {
 131                              // Do not send buffered external events to internal handlers,
 132                              // they processed them already.
 133                              continue;
 134                          }
 135                      } else {
 136                          if ($DB->is_transaction_started()) {
 137                              if ($fromextbuffer) {
 138                                  // Weird!
 139                                  continue;
 140                              }
 141                              // Do not notify external observers while in DB transaction.
 142                              if (!$addedtoextbuffer) {
 143                                  self::$extbuffer[] = $event;
 144                                  $addedtoextbuffer = true;
 145                              }
 146                              continue;
 147                          }
 148                      }
 149  
 150                      if (isset($observer->includefile) and file_exists($observer->includefile)) {
 151                          include_once($observer->includefile);
 152                      }
 153                      if (is_callable($observer->callable)) {
 154                          try {
 155                              call_user_func($observer->callable, $event);
 156                          } catch (\Exception $e) {
 157                              // Observers are notified before installation and upgrade, this may throw errors.
 158                              if (empty($CFG->upgraderunning)) {
 159                                  // Ignore errors during upgrade, otherwise warn developers.
 160                                  debugging("Exception encountered in event observer '$observer->callable': ".$e->getMessage(), DEBUG_DEVELOPER, $e->getTrace());
 161                              }
 162                          }
 163                      } else {
 164                          debugging("Can not execute event observer '$observer->callable'");
 165                      }
 166                  }
 167              }
 168  
 169              // TODO: Invent some infinite loop protection in case events cross-trigger one another.
 170          }
 171      }
 172  
 173      /**
 174       * Returns list of classes related to this event.
 175       * @param \core\event\base $event
 176       * @return array
 177       */
 178      protected static function get_observing_classes(\core\event\base $event) {
 179          $observers = array('\core\event\base', '\\'.get_class($event));
 180          return $observers;
 181  
 182          // Note if we ever decide to observe events by parent class name use the following code instead.
 183          /*
 184          $classname = get_class($event);
 185          $observers = array('\\'.$classname);
 186          while ($classname = get_parent_class($classname)) {
 187              $observers[] = '\\'.$classname;
 188          }
 189          $observers = array_reverse($observers, false);
 190  
 191          return $observers;
 192          */
 193      }
 194  
 195      /**
 196       * Initialise the list of observers.
 197       */
 198      protected static function init_all_observers() {
 199          global $CFG;
 200  
 201          if (is_array(self::$allobservers)) {
 202              return;
 203          }
 204  
 205          if (!PHPUNIT_TEST and !during_initial_install()) {
 206              $cache = \cache::make('core', 'observers');
 207              $cached = $cache->get('all');
 208              $dirroot = $cache->get('dirroot');
 209              if ($dirroot === $CFG->dirroot and is_array($cached)) {
 210                  self::$allobservers = $cached;
 211                  return;
 212              }
 213          }
 214  
 215          self::$allobservers = array();
 216  
 217          $plugintypes = \core_component::get_plugin_types();
 218          $plugintypes = array_merge(array('core' => 'not used'), $plugintypes);
 219          $systemdone = false;
 220          foreach ($plugintypes as $plugintype => $ignored) {
 221              if ($plugintype === 'core') {
 222                  $plugins['core'] = "$CFG->dirroot/lib";
 223              } else {
 224                  $plugins = \core_component::get_plugin_list($plugintype);
 225              }
 226  
 227              foreach ($plugins as $plugin => $fulldir) {
 228                  if (!file_exists("$fulldir/db/events.php")) {
 229                      continue;
 230                  }
 231                  $observers = null;
 232                  include("$fulldir/db/events.php");
 233                  if (!is_array($observers)) {
 234                      continue;
 235                  }
 236                  self::add_observers($observers, "$fulldir/db/events.php", $plugintype, $plugin);
 237              }
 238          }
 239  
 240          self::order_all_observers();
 241  
 242          if (!PHPUNIT_TEST and !during_initial_install()) {
 243              $cache->set('all', self::$allobservers);
 244              $cache->set('dirroot', $CFG->dirroot);
 245          }
 246      }
 247  
 248      /**
 249       * Add observers.
 250       * @param array $observers
 251       * @param string $file
 252       * @param string $plugintype Plugin type of the observer.
 253       * @param string $plugin Plugin of the observer.
 254       */
 255      protected static function add_observers(array $observers, $file, $plugintype = null, $plugin = null) {
 256          global $CFG;
 257  
 258          foreach ($observers as $observer) {
 259              if (empty($observer['eventname']) or !is_string($observer['eventname'])) {
 260                  debugging("Invalid 'eventname' detected in $file observer definition", DEBUG_DEVELOPER);
 261                  continue;
 262              }
 263              if ($observer['eventname'] === '*') {
 264                  $observer['eventname'] = '\core\event\base';
 265              }
 266              if (strpos($observer['eventname'], '\\') !== 0) {
 267                  $observer['eventname'] = '\\'.$observer['eventname'];
 268              }
 269              if (empty($observer['callback'])) {
 270                  debugging("Invalid 'callback' detected in $file observer definition", DEBUG_DEVELOPER);
 271                  continue;
 272              }
 273              $o = new \stdClass();
 274              $o->callable = $observer['callback'];
 275              if (!isset($observer['priority'])) {
 276                  $o->priority = 0;
 277              } else {
 278                  $o->priority = (int)$observer['priority'];
 279              }
 280              if (!isset($observer['internal'])) {
 281                  $o->internal = true;
 282              } else {
 283                  $o->internal = (bool)$observer['internal'];
 284              }
 285              if (empty($observer['includefile'])) {
 286                  $o->includefile = null;
 287              } else {
 288                  if ($CFG->admin !== 'admin' and strpos($observer['includefile'], '/admin/') === 0) {
 289                      $observer['includefile'] = preg_replace('|^/admin/|', '/'.$CFG->admin.'/', $observer['includefile']);
 290                  }
 291                  $observer['includefile'] = $CFG->dirroot . '/' . ltrim($observer['includefile'], '/');
 292                  if (!file_exists($observer['includefile'])) {
 293                      debugging("Invalid 'includefile' detected in $file observer definition", DEBUG_DEVELOPER);
 294                      continue;
 295                  }
 296                  $o->includefile = $observer['includefile'];
 297              }
 298              $o->plugintype = $plugintype;
 299              $o->plugin = $plugin;
 300              self::$allobservers[$observer['eventname']][] = $o;
 301          }
 302      }
 303  
 304      /**
 305       * Reorder observers to allow quick lookup of observer for each event.
 306       */
 307      protected static function order_all_observers() {
 308          foreach (self::$allobservers as $classname => $observers) {
 309              \core_collator::asort_objects_by_property($observers, 'priority', \core_collator::SORT_NUMERIC);
 310              self::$allobservers[$classname] = array_reverse($observers);
 311          }
 312      }
 313  
 314      /**
 315       * Returns all observers in the system. This is only for use for reporting on the list of observers in the system.
 316       *
 317       * @access private
 318       * @return array An array of stdClass with all core observer details.
 319       */
 320      public static function get_all_observers() {
 321          self::init_all_observers();
 322          return self::$allobservers;
 323      }
 324  
 325      /**
 326       * Replace all standard observers.
 327       * @param array $observers
 328       * @return array
 329       *
 330       * @throws \coding_Exception if used outside of unit tests.
 331       */
 332      public static function phpunit_replace_observers(array $observers) {
 333          if (!PHPUNIT_TEST) {
 334              throw new \coding_exception('Cannot override event observers outside of phpunit tests!');
 335          }
 336  
 337          self::phpunit_reset();
 338          self::$allobservers = array();
 339          self::$reloadaftertest = true;
 340  
 341          self::add_observers($observers, 'phpunit');
 342          self::order_all_observers();
 343  
 344          return self::$allobservers;
 345      }
 346  
 347      /**
 348       * Reset everything if necessary.
 349       * @private
 350       *
 351       * @throws \coding_Exception if used outside of unit tests.
 352       */
 353      public static function phpunit_reset() {
 354          if (!PHPUNIT_TEST) {
 355              throw new \coding_exception('Cannot reset event manager outside of phpunit tests!');
 356          }
 357          self::$buffer = array();
 358          self::$extbuffer = array();
 359          self::$dispatching = false;
 360          if (!self::$reloadaftertest) {
 361              self::$allobservers = null;
 362          }
 363          self::$reloadaftertest = false;
 364      }
 365  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1