<?php

header
('Content-Type: text/html; charset=UTF-8');
echo 
'<pre>';


/**
 * Classe abstraite `Observable`.
 */
abstract class AbstractObservable {
    
    
/**
     * Contiendra les noms des évènements existants pour l'objet courant.
     * Il est conseillé d'ajouter les noms des évènements disponibles via la méthode 
     * `__construct()` de la manière suivante : `$this->_events += array('event1', 'event2');`
     *
     * @var array
     */
    
protected $_events = array();
    
    
/**
     * Contiendra les fonctions de Callback à éxécuter lorsqu'un évènement est déclanché.
     * 
     * @var array
     */
    
protected $_observers = array();
    
    
/**
     * Ajoute l'observateur `$observer` à l'évènement `$event` de l'objet courant.
     *
     * @param string $event Le nom de l'évènement à observer.
     * @param callback $observer La fonction de callback à lier à l'évènement.
     * @return object Retourne l'objet courant.
     */
    
public function addObserver($event$observer) {
        if (!
is_callable($observer)) {
            throw new 
Exception("Le paramètre \$observer n'est pas valide.");
        }
        if (!
in_array($event$this->_events)) {
            throw new 
Exception("\$event n\'est pas un évènement valide.");
        }
        if (!
array_key_exists($event$this->_observers)) {
            
$this->_observers[$event] = array();
        }
        
// Évite d'ajouter plusieurs fois le même "observateur"
        
if (!array_search($observer$this->_observers[$event], true)) {
            
$this->_observers[$event][] = $observer;
        }
        return 
$this;
    }
    
    
/**
     * Supprime l'observateur `$observer` de l'évènement `$event` de l'object courant.
     * 
     * @param string $event Le nom de l'évènement à ne plus observer.
     * @param callback $observer La fonction de callback à delier de l'évènement.
     * @return object Retourne l'objet courant.
     */
    
public function removeObserver($event$observer) {
        if (!
is_callable($observer)) {
            throw new 
Exception("Le paramètre \$observer n'est pas valide.");
        }
        if (
array_key_exists($event$this->_observers
            && 
is_array($this->_observers[$event])
        ) {
            
$key array_search($observer$this->_observers[$event], true);
            if (
is_int($key)) {
                unset(
$this->_observers[$event][$key]);
            }
        }
        return 
$this;
    }
    
    
/**
     * Ajoute les observateurs `$observers` à l'objet courant.
     *
     * @param array $observers Tableau associatif d'observateurs au format <type> => <callback>
     *        à ajouter à l'objet courant.
     * @return object Retourne l'objet courant.
     */
    
public function addObservers($observers) {
        foreach (
$observers as $event => $observer) {
            
$this->addObserver($event$observer);
        }
        return 
$this;
    }
    
    
/**
     * Supprime les observateurs de l'évènement `$event` de l'objet courant.
     * Si `$event` n'est pas spécifié, tous les observateurs (de tous les évènements) de l'objet
     * courant seront supprimés.
     *
     * @param string $event Le nom de l'évènement à ne plus observer.
     * @return object Retourne l'objet courant.
     */
    
public function removeObservers($event false) {
        if (
$event) {
            if (!
array_key_exists($event$this->_observers
                && !
in_array($event$this->_events)
            ) {
                throw new 
Exception("\$event n\'est pas un évènement valide.");
            }
            if (
array_key_exists($event$this->_observers)) {
                
$this->_observers[$event] = array();
            }
        } else {
            
$this->_observers = array();
        }
        return 
$this;
    }
    
    
/**
     * Émet l'évènement `$event` et exécute les fonctions de callback lié à cet évènement.
     *
     * @param $event Le nom de l'évènement à émettre.
     * @return object Retourne l'objet courant.
     */
    
protected function _fireEvent($event) { // , $args1 , $args2 , ...
        
if (!in_array($event$this->_events)) {
            throw new 
Exception("\$event n\'est pas un évènement valide.");
        }
        if (
array_key_exists($event$this->_observers)) {
            
// func_get_args returns copy of arguments
            // $args = func_get_args();
            // debug_backtrace returns arguments by reference           
            
$stack debug_backtrace();
            
$args  = & $stack[0]['args'];
            
$args[0] = $this;
            
            foreach (
$this->_observers[$event] as $observer) {
                
call_user_func_array($observer$args);
            }
        }
        return 
$this;
    }
}





class 
StationSki extends AbstractObservable {
    
    private 
$_ouverture false;
    
    public function 
__construct() {
        
// Défini les évènements disponibles pour cet objet
        
$this->_events += array('ouvertureChange');
    }
    
    public function 
setOuverture($ouverture) {
        if (
$this->_ouverture != $ouverture) {
            
$this->_ouverture $ouverture;
            
$this->_fireEvent('ouvertureChange', & $this->_ouverture);
        }
    }
    
    public function 
getOuverture() {
        return 
$this->_ouverture;
    }
}


class 
SiteStation {
    
    public function 
onStationOuvertureChange($stationSki) {
        if (
$stationSki->getOuverture()) {
            
write("On signale sur le site que la station est maintenant ouverte."true);
        } else {
            
write("On signale sur le site que la station est maintenant fermée."true);
        }
    }
}


class 
Skieurs {
    
    public function 
onStationOuvertureChange($stationSki$ouverture) {
        if (
$ouverture) {
            
write("On averti les abonnés par email que la station est maintenant ouverte."true);
        } else {
            
write("On averti les abonnés par email que la station est maintenant fermée."true);
        }
    }
}





function 
write($str$inCallback=false) {
    
$color $inCallback 'green' 'blue';
    if (
$inCallback) {
        
$str '&gt; ' $str;
    }
    
printf('<span style="color:%s">%s</span><br/>'$color$str);
}





write('$stationSki  = new StationSki();');
$stationSki = new StationSki();

write('$siteStation = new SiteStation();');
$siteStation = new SiteStation();

write('$skieurs     = new Skieurs();');
$skieurs = new Skieurs();

write('$stationSki->addObserver(\'ouvertureChange\', array($siteStation, \'onStationOuvertureChange\'));');
$stationSki->addObserver('ouvertureChange', array($siteStation'onStationOuvertureChange'));

write('$stationSki->setOuverture(true);');
$stationSki->setOuverture(true);

write('$stationSki->addObserver(\'ouvertureChange\', array($skieurs, \'onStationOuvertureChange\'));');
$stationSki->addObserver('ouvertureChange', array($skieurs'onStationOuvertureChange'));

write('$stationSki->setOuverture(false);');
$stationSki->setOuverture(false);



write('$stationSki->removeObserver(\'ouvertureChange\', array($siteStation, \'onStationOuvertureChange\'));');
$stationSki->removeObserver('ouvertureChange', array($siteStation'onStationOuvertureChange'));

write('$stationSki->setOuverture(true);');
$stationSki->setOuverture(true);