Christian Seiler: Lese ich das richtig? PHP-Objekte ähnlich flexibel wie in JS?

Beitrag lesen

Hallo nochmal,

Ich hab übrigens mal was schnell zusammengehackt, was vielleicht das Leben erleichtert. Ich behaupte nicht, dass es perfekt ist, man kann da sicher noch einige Dinge tweaken, aber es ist vielleicht ganz nett:

////////////////////////////////////////////////////////////////////  
// BASISCODE, DER NICHT GEAENDERT WERDEN MUSS  
////////////////////////////////////////////////////////////////////  
  
class ExtensibleObject {  
  private static $prototypeMethods = array ();  
  
  public function __call ($method, $args) {  
    if (!isset ($this->$method)) {  
      $class = get_class ($this);  
      if (!isset (self::$prototypeMethods[$class]) || !isset (self::$prototypeMethods[$class]->$method)) {  
        throw new BadMethodCallException ("Method $class::$method does not exist");  
      }  
      $method = self::$prototypeMethods[$class]->$method;  
    } else {  
      $method = $this->$method;  
    }  
    try {  
      $p = new ReflectionParameter ($method, 0);  
      if ($p->getName () == 'self') {  
        // Python-Style convention: First parameter is named $self  
        array_unshift ($args, $this);  
      }  
    } catch (ReflectionException $e) {  
      // ignore (parameter count < 0)  
    }  
    return call_user_func_array ($method, $args);  
  }  
  
  public static function __callStatic ($name, $args) {  
    $class = get_called_class ();  
    if (!isset (self::$prototypeMethods[$class]) || !isset (self::$prototypeMethods[$class]->$name)) {  
      throw new BadMethodCallException ("Method $class::$name does not exist");  
    }  
    $method = self::$prototypeMethods[$class]->$name;  
    try {  
      $p = new ReflectionParameter ($method, 0);  
      if ($p->getName () == 'self') {  
        // Python-Style convention: First parameter is named $self  
        throw new BadMethodCallException ("Non-static method $class::$name may not be called statically");  
      }  
    } catch (ReflectionException $e) {  
      // ignore (parameter count < 0)  
    }  
    return call_user_func_array ($method, $args);  
  }  
  
  public function __get ($name) {  
    if ($name == 'prototype') {  
      return static::prototype ();  
    } else {  
      $class = get_class ($this);  
      throw new RuntimeException ("Attribute $class::$name does not exist");  
    }  
  }  
  
  public static function prototype () {  
    $class = get_called_class ();  
    if (!isset (self::$prototypeMethods[$class])) {  
      self::$prototypeMethods[$class] = new stdClass;  
    }  
    return self::$prototypeMethods[$class];  
  }  
}  
  
////////////////////////////////////////////////////////////////////  
// ENDE  
////////////////////////////////////////////////////////////////////  
  
// Abgeleitete Klasse  
class MyObject extends ExtensibleObject {  
  public $welt = 'Welt';  
}  
  
// Etwas prototypisches Zeugs  
$obj1 = new MyObject;  
$obj1->prototype->hallo = function ($self) {  
  echo "Hallo ".$self->welt."\n";  
};  
MyObject::prototype ()->ciao = function ($self) {  
  echo "Ciao ".$self->welt."\n";  
};  
$obj1->prototype->tag = function () {  
  echo "Guten Tag!\n";  
};  
$obj1->nacht = function () {  
  echo "Gute Nacht!\n";  
};  
$obj2 = new MyObject;  
$obj2->welt = 'kleine Welt';  
  
echo "Hallo:\n===============\n";  
$obj1->hallo ();  
$obj2->hallo ();  
echo "\nCiao:\n===============\n";  
$obj1->ciao ();  
$obj2->ciao ();  
echo "\nTag:\n===============\n";  
$obj1->tag ();  
$obj2->tag ();  
echo "\nNacht:\n===============\n";  
$obj1->nacht ();  
try {  
  $obj2->nacht ();  
} catch (Exception $e) {  
  echo "Exception: ". $e->getMessage () ."\n";  
}  
echo "\nTag (static):\n===============\n";  
MyObject::tag ();  
echo "\nHallo (static):\n===============\n";  
try {  
  MyObject::hallo ();  
} catch (Exception $e) {  
  echo "Exception: ". $e->getMessage () ."\n";  
}

Die Ausgabe bei PHP 5.3 wäre folgende:

Hallo:

Hallo Welt
Hallo kleine Welt

Ciao:

Ciao Welt
Ciao kleine Welt

Tag:

Guten Tag!
Guten Tag!

Nacht:

Gute Nacht!
Exception: Method MyObject::nacht does not exist

Tag (static):

Guten Tag!

Hallo (static):

Exception: Non-static method MyObject::hallo may not be called statically

Das ganze kombiniert hier schön Late Static Bindings (get_called_class (), static::) mit Closures und __call/__callStatic-Logik und demonstriert damit einige der neuen Features von PHP 5.3. Ich hoffe, der Code erklärt sich selbst. Was es noch nicht kann, was man aber sicher leicht hinzufügen könnte, wäre $x->prototype = $y->prototype (bzw. für den statischen Fall Class2::setPrototype (Class1::prototype ())) mit spezieller Klasse ExtensibleObjektPrototype für Type-Hints. Vielleicht hacke ich das bei Gelegenheit auch noch zusammen.

Achja, falls man __get/__call in eigenen Klassen überschreibt, sollte das so aussehen:

class MySubclass extends ExtensibleObject {  
  public function __get ($name) {  
    try {  
      return parent::__get ($name);  
    } catch (RuntimeException $e) {}  
    // eigentlicher Code fängt hier an, sobald die RuntimeException  
    // ignoriert wurde  
    return $this->foobar[$name]; // oder whatever  
  }  
  public function __call ($method, $args) {  
    try {  
      return parent::__call ($method, $args);  
    } catch (BadMethodCallException $e) {}  
    // eigentlicher Code fängt hier an, sobald die BadMethodCallException  
    // ignoriert wurde  
    return $this->foobar[$method] ($args); // oder whatever  
  }  
}

Viele Grüße,
Christian

--
Mein "Weblog" [RSS]
Using XSLT to create JSON output (Saxon-B 9.0 for Java)
»I don't believe you can call yourself a web developer until you've built an app that uses hyperlinks for deletion and have all your data deleted by a search bot.«
            -- Kommentar bei TDWTF