Ανάπτυξη ιστοτόπου σε περιβάλλον Debian/dokuwiki/Σύστημα συμβάντων

Γενικά επεξεργασία

Βρόχος συμβάντων επεξεργασία

Όταν το Dokuwiki πρόκειται να εκτελέσει κάποια ενέργεια (να εκτελέσει κώδικα που επιτελεί ένα αναγνωρίσιμο έργο) η οποία συσχετίζεται με κάποιο συμβάν (event) δεν την πραγματοποιεί αμμέσα, αλλά λέμε ότι πυροδοτεί το γεγονός(trigger event) . Αυτό σημαίνει ότι η ενέργεια αυτή θα εκτελεστεί σε ένα συγκεκριμένο πλαίσιο που περιλαμβάνει την δυνατότητα να εκτελεστούν και αλλες συναρτήσεις που συνδέσαμε με αυτό το συμβάν είτε για να εκτελεστούν πριν ή μετά την βασική προκαθορισμένη ενέργεια. Έτσι για παράδειγμα όταν το Dokuwiki πρόκειται να γράψει μια βικίσελίδα δεν την γράφει άμμεσα αλλά μέσω ενός βήματος (γραφειοκρατία ε;;) που ονομάζουμε πυροδότηση γεγονότος IO_WIKIPAGE_WRITE ,η οποία πυροδότηση (μπορούμε να την σκεφτούμε σαν μια ενδιάμεση κλήση γραφειοκρατικής συνάρτησης :-) ) , θα εκτελέσει την ενέργεια που θέλουμε αφού όμως εξετάσει παράλληλα να δεί αν έχουν συνδεθεί άλλες συναρτήσεις χειριστές (hooks/handlers) με αυτό το συμβάν ,κάτι που μπορεί να έχουν κάνει πρόσθετα (plugins).

Συνεπώς μπορούμε να φανταστούμε ότι στον κώδικα (σαν ένα μεγάλο κομμάτι χαρτί) ξεχωρίζουμε κάποια τμήματά του που είναι κάποιες ενέργειες που θεωρούμε σημαντικές και φροντίζουμε ώστε να εκτελούνται όχι άμμεσα αλλά έμμεσα , αφού τις συσχετίσουμε με ένα συμβάν (βασικά δίνουμε ένα όνομα στην ενέργεια) και κατόπιν καλέσουμε μια συνάρτηση trigger_event("όνομα γεγονότος",..) η οποία θα την εκτελέσει αλλά επίσης θα κοιτάξει να δει άν έχουν συνδεθεί και άλλες ενέργειες-συναρτήσεις οπότε θα τις εκτελέσει και αυτές (πριν ή μετά την βασική ενέργεια ανάλογα με το πώς έγινε η σύνδεση).

Η συνάρτηση trigger_event συμβουλεύται για την εκτελεσή της μια λίστα (FCFS -first come first served) στην οποία καταχωρούν οι ενδιαφερόμενες συναρτήσεις το συμβάν με το οποίο θέλουν να συνδεθούν. Στην πραγματικότητα υπάρχουν δυο λίστες που ονομάζονται BEFORE και AFTER . Αυτές οι λέξεις χρησιμοποιούνται και όταν καταχωρούμαι μια συνάρτηση για ένα γεγονός .Πριν εκτελέσει τις δικές του ενέργειες το DokuWiki με έναν βρόχο σαρώνει όλους τους BEFORE χειριστές και στο τέλος εκτελεί την δικιά του ενέργεια. Αυτό δίνει τη δυνατότητα σε έναν προγραμματιστή να προσθέσει λειτουργικότητα πριν τη βασική ενέργεια. Πχ στην περίπτωση του συμβάντος IO_WIKIPAGE_WRITE, θα μπορούσε ένα πρόσθετο να κάνει κάποιες αλλαγές πριν σταλθεί η σελίδα στον πλοηγό.

Αφού εκτελέσει την βασική του ενέργεια το DokuWiki σαρώνει μέσω ενός άλλου βρόχου όλους τους χειριστές AFTER . Πχ στην περίπτωση του συμβάντος TPL_ACT_RENDER είναι δυνατό να προστεθεό περιεχόμνενο στην βικισελίδα.

Ας σημειωθεί επίσης ότι είναι δυνατό ένας χειριστής BEFORE μπορεί να ακυρώσει την εκτέλεση της προκαθορισμένης-βασικής. Επίσης μπορεί ένας χειριστής να σταματήσει την περαιτέρω επεξεργασία των λιστών BEFORE , AFTER εμποδίζωντας την εκτέλεση άλλων χειριστών.

Μπορούμε να αποδώσουμε σε απλοποιημένο ψευδοκώδικα την διαδικασία χειρισμού τον συμβάντων μέσω της διαδικασίας της πυροδότησης.

     var $process_event = true; 
     var $default_stopped = false;

     loop_through_BEFORE_List() {
         return if $process_event == false;
     }

     do_DokuWiki_Action() {
         return if $default_stopped;
     }
     
     var $process_event = true; 

     loop_through_AFTER_List(){
      return if $process_event == false;
     }

Η αρχιτεκτονική του dokuwiki βασίζεται στην πυροδότητηση συμβάντων(events) και στην επεξεργασία συναρτήσεων που έχουν προσδεθεί σ'αυτά τα γεγονότα.

Δηλαδή θεωρούμε ότι στην λειτουργία του dk υπάρχουν κάποια κομβικά σημεία τα οποία αποκαλούμε συμβάντα (events) και τα οποία για να συμβούν πρέπει να πυροδοτήσουμε ένα συμβάν. (trigger an event). Και γιατί να μην μπορούμε να εκτελούμε τον κώδικά μας κατά την δική μας επιθυμία; Αυτό που κερδίζουμε είναι η επεκτασιμότητα στον κώδικά μας αφού μπορούμε πλεον να προσθέτουμε και δικές μας διαδικασίες χειριστές οι οποίες θα εκτελούνται πριν ή και μετά την προκαθορισμένη συνάρτηση του γεγονότος.

Το dokuwiki χρησιμοποεί σαν προγραμματιστικό παράδειγμα ένα μίγμα από αντικειμενοστραφή κώδικα της php , μια λογική event-driven παραδείγματος ( οπότε συναντούμε στον κώδικα έννοιες όπως event handler event object κτλ.) και aspect-oriented παραδείγματος (συντώντας έννοιες όπως advice).

Με χρήση λοιπόν εννοιολογικών πλαισίων και πρακτικών από τα παραπάνω παραδείγματα οικοδομείται ο πυρήνας του dokuwiki που συνίσταται από γεγονότα που συμβαίνουν των οποίων όμως ο χειρισμός μπορεί να αλλάζει είτε προσθέτοντας καινούργιους χειριστές τους είτε αλλάζωντας τους υπάρχοντες.

Πυροδότηση συμβάντων επεξεργασία

Όταν θέλουμε να δημιουργηθεί ένα γεγονός καλείται η trigger_event η οποία αυτό που κάνει είναι να δημιουργεί ένα αντικείμενο event και να καλεί την trigger σε αυτό.

Μπορείτε να πυροδοτήσετε συμβάντα με τρεις τρόπους
Ο ποιό απλός είναι με την συνάρτηση trigger_event.Η σνάρτηση αυτή δέχεται όλα τα απαραίτητα ορίσματα για να δημιουργήσει ένα συμβάν και να το πυροδοτήσει.
function trigger_event($name, &$data, $action=NULL, $canPreventDefault=true) {
  $evt = new Doku_Event($name, $data);
  return $evt->trigger($action, $canPreventDefault);
}
//$name               (string)   όνομα του συμβάντος(event)
//$data               (mixed)    δεδομένα συμβάντος
//$action             (callback) (προεραιτικό όρισμα, προκαθορισμένη τιμη=NULL) μια 
//                     προκαθορισμένη συνάρτηση ανάκλησης(callback)
//$canPreventDefault  (bool)     (προεραιτικό όρισμα, προκαθορισμένη τιμη=true) οι 
//                    συναρτήσεις συνδεδεμένες σε συμβάντα συναρτήσεις μπορούν 
//                    να παρεμποδίσουν την  προκαθορισμένη ενέργεια
//RETURN (mixed) το αποτέλεσμα του συμβάντος αφού ολοκληρωθεί η επεξεργασία 
//του συμβάντος ,η προκαθορισμένη τιμή είναι η τιμή επιστροφής της προκαθορι-
//σμένης ενέργειας αλλά μπορεί να αλλάξει από τις συνδεδεμένες στο συμβάν 
//συναρτήσεις

ή

Με χρήση της μεθόδου πυροδότησης(trigger) του αντικειμένου event(συμβαν). Προυποθέτουμε

λοιπόν ότι έχουμε δημιουργήσει το αντικείμενο συμβάν και μετά καλούμε την μέθοδό του.

$evt = new Doku_Event(<event_name>,<event_data>);
$evt->trigger(<default action>,<can prevent default>);
unset($evt);

ή

Χειριζόμαστε ολόκληρη την διαδικασία της πυροδότησης του συμβάντος μόνοι μας. Αυτό είναι

χρήσιμο όταν υπάρχει μια προκαθορισμένη μέθοδος αλλά δεν μπορούμε ή δυσκολευόμαστε να την πακετάρουμε σαν συνάρτηση ανάκλησης PHP.

$evt = new Doku_Event(<event name>, <event data>);
if ($evt->advise_before(<can prevent default>)) {
// default actioncode block
}
$evt->advise_after();
unset($evt);
οκ


Σημαντικά αντικείμενα, δομές δεδομένων επεξεργασία

Δυο από τα αντικείμενα στα οποία πρέπει να επικεντρώσουμε την προσοχή μας είναι τα:

Doku_Event επεξεργασία

O php κώδικας του αντικειμένου συμβάν
class Doku_Event {

  // public properties
  var $name = '';                // READONLY  event name, objects must register against this name to see the event
  var $data = NULL;              // READWRITE data relevant to the event, no standardised format (YET!)
  var $result = NULL;            // READWRITE the results of the event action, only relevant in "_AFTER" advise
                                 //    event handlers may modify this if they are preventing the default action
                                 //    to provide the after event handlers with event results
  var $canPreventDefault = true; // READONLY  if true, event handlers can prevent the events default action

  // private properties, event handlers can effect these through the provided methods
  var $_default = true;     // whether or not to carry out the default action associated with the event
  var $_continue = true;    // whether or not to continue propagating the event to other handlers

  /**
   * event constructor
   */
  function Doku_Event($name, &$data) {

    $this->name = $name;
    $this->data =& $data;

  }

  function advise_before($enablePreventDefault=true) {
    global $EVENT_HANDLER;

    $this->canPreventDefault = $enablePreventDefault;
    $EVENT_HANDLER->process_event($this,'BEFORE');

    return (!$enablePreventDefault || $this->_default);
  }

  function advise_after() {
    global $EVENT_HANDLER;

    $this->_continue = true;
    $EVENT_HANDLER->process_event($this,'AFTER');
  }

  function trigger($action=NULL, $enablePrevent=true) {

    if (!is_callable($action)) $enablePrevent = false;

    if ($this->advise_before($enablePrevent) && is_callable($action)) {
      if (is_array($action)) {
        list($obj,$method) = $action;
        $this->result = $obj->$method($this->data);
      } else {
        $this->result = $action($this->data);
      }
    }

    $this->advise_after();

    return $this->result;
  }

  function stopPropagation() { $this->_continue = false;  }
  function preventDefault() { $this->_default = false;  }
}


Όπως ξέρουμε από το οντοκεντρικό παράδειγμα ένα αντικείμενο έχει τις ιδιότητες του και τις μεθόδους του. Οι μέθοδοι είναι ενέργειες που μπορούμε να κάνουμε πάνω στο αντικείμενο. Έτσι η ποιό σημαντική ενέργεια που μπορούμε να κάνουμε πάνω σε ένα dokuwiki γεγονός είναι να το πυροδοτήσουμε. Δηλαδή χρησιμοποιώντας όρους του υποδείγματος η δημιουργία του αντικειμένου δεν σημαίνει ότι πυροδοτήθηκε ότι έγινε γνωστό δηλαδή στους χειριστές οι οποίοι θα το συλλάβουν και θα εκτελέσουν ανάλογες εντολές. Συνεπώς η ποίο σημαντική ενέργεια ενός γεγονός είναι η πυροδότηση (event.trigger).

Το 'Doku_Event θα δημιουργηθεί όποτε θέλουμε να πυροδοτηθεί ένα γεγονός. Και περιέχει και την αντίστοιχη μέθοδο trigger . Όμως η trigger δεν ξέρει πως να χειριστεί αυτό το γεγονός. Αυτό που κάνει είναι να ενημερώνει ένα άλλο αντικείμενο , τον Doku_Event_Handler, ο οποίος ξέρει ποιά αντίστοιχη συνάρτηση να εκτελέσει.

Doku_Event_Handler επεξεργασία

O php κώδικας του χειριστή συμβάντων
class Doku_Event_Handler {

  // public properties:  none

  // private properties
  var $_hooks = array();          // array of events and their registered handlers


  function Doku_Event_Handler() {

    // load action plugins
    $plugin = NULL;
    $pluginlist = plugin_list('action');

    foreach ($pluginlist as $plugin_name) {
      $plugin =& plugin_load('action',$plugin_name);

      if ($plugin !== NULL) $plugin->register($this);
    }
  }


  function register_hook($event, $advise, &$obj, $method, $param=NULL) {
    $this->_hooks[$event.'_'.$advise][] = array(&$obj, $method, $param);
  }

  function process_event(&$event,$advise='') {

    $evt_name = $event->name . ($advise ? '_'.$advise : '_BEFORE');

    if (!empty($this->_hooks[$evt_name])) {
      $hook = reset($this->_hooks[$evt_name]);
      do {
//        list($obj, $method, $param) = $hook;
        $obj =& $hook[0];
        $method = $hook[1];
        $param = $hook[2];

        if (is_null($obj)) {
          $method($event, $param);
        } else {
          $obj->$method($event, $param);
        }

      } while ($event->_continue && $hook = next($this->_hooks[$evt_name]));
    }
  }
}


$EVENT_HANDLER επεξεργασία

  Δημιουργία του χειριστή συμβάντων.
global $EVENT_HANDLER;
$EVENT_HANDLER = new Doku_Event_Handler();
Ο χειριστής συμβάντων είναι ένα αντικείμενο που περιέχει τις αντιστοιχίσεις ανάμεσα σε συμβάντα και συνδεδεμένες συναρτήσεις στον πίνακα $_hook καθώς και μεθόδους αρχικοποίησης δημιουργίας και προσθήκης χειριστών.


Αυτό το αντικείμενο περιέχει έναν πίνακα που αντιστοιχεί γεγονότα σε συναρτήσεις χειριστές και παρέχει δυο βασικές μεθόδους.

  1. για προσθήκη χειριστών για κάποιο γεγονός
  2. για την επεξεργασία-εκτέλεση ενός χειριστή


Παραδείγματα επεξεργασία

TPL_METAHEADER_OUTPUT επεξεργασία

Στο αρχείο DOKUINC/lib/tmp/templatename/main.php αφού δημιουργηθούν οι πίνακες με τις απαραίτητες πληροφορίες $head['meta'] $head['link'] κτλ καλείται η συνάρτηση :

trigger_event('TPL_METAHEADER_OUTPUT',$head,'_tpl_metaheaders_action',true);

Δηλαδή θα δημιουργηθεί το συμβάν TPL_METAHEADER_OUTPUT κσι θα πυροδοτηθεί, δηλαδή θα εξεταστεί ο χειριστής για τυχών συνδεδεμένες στον συμβάν συναρτήσεις και ανάλογα θα τις εκτελέσει πριν ή μετά την προκαθορισμένη εκτέλεση της _tpl_metaheaders_action Η συνάρτηση _tpl_metaheaders_action θα παράγει σαν έξοδο με την εντολή echo τα στοιχεία xhtml της επικεφαλίδας του εγγράφου μας.