Es ist ein Model und es sieht gut aus – Die Magie – Part 1

| 1 Kommentar

Nachdem wir im Artikel über die Grundstruktur unserer Modelklasse die Basis geschaffen haben, geht es heute wie versprochen um die Implementierung der magischen Methoden.

Wie beim letzen Mal zeichne ich am besten zuerst wieder das Gerüst der neuen, meist “magischen”, Methoden.

<?php
class Custom_Model implements Custom_Model_Interface
{
  // ...
  protected $_outputFormat;

  public function __call() {}
  public function __get() {}
  public function __set() {}
  public function __isset() {}
  public function __unset() {}
  public function __toString() {}
  public function format() {}
  public function outputFormat() {}
  // ...
}

Ok, zugegeben, das ist nur ein Teil der Möglichkeiten, aber im Moment für unsere Zwecke ausreichend und ich muss mir ja auch noch Material für weitere Artikel aufheben. ;-)

Was können wir nun damit erreichen und wofür ist das gut? Magische Methoden ermöglichen uns das Verhalten unserer Objekte unter bestimmten Umständen festzulegen.

Dank __call() kann unser Objekt auch Aufrufe nicht definierter Methoden verarbeiten. Wir machen uns das zu Nutze, indem wir für jede Eigenschaft dynamische Zugriffsmethoden bereitstellen.

public function __call($method, $args)
{
  if (preg_match('/^get([a-zA-Z0-9]+)$/i', $method, $matches)) {
    return $this->_get($matches[1]);
  }

  if (preg_match('/^set([a-zA-Z0-9]+)$/i', $method, $matches)) {
    return $this->_set($matches[1], $args[0]);
  }

  $message = 'Call to undefined method '
           . get_class($this) . '::' . $method . '()';
  trigger_error($message, E_USER_ERROR);
}

Die Argumentenliste von __call() enthält den Namen der aufgerufenden Methode und ein Array mit deren Argumenten.

Unsere Implementierung reagiert nun auf alle Methodennamen, die mit “get” oder “set” beginnen und löst für alles andere einen Fehler aus.

Nun können wir unsere Objekteigenschaften über “Getter” und “Setter” lesen und schreiben.

$model->setId(42);  // schreiben
echo $model->getId();  // lesen

Im Moment wird hier aber noch nicht geprüft, ob die Eigenschaft existiert oder ein Wert für “set” übergeben wurde.

Ähnlich funktioniert es mit den magischen Zugriffsmethoden __get() und __set(), nur dass diese den Zugriff auf nicht definierte Eigenschaften kontrollieren und diesen bei unserer Klasse auch an _get() und _set() weiterleiten.

public function __get($key)
{
  return $this->_get($key);
}

public function __set($key, $value)
{
  return $this->_set($key, $value);
}

Nun lassen sich die Eigenschaften scheinbar auch direkt über das Objekt ansprechen.

$model->id = 42;  // schreiben
echo $model->id;  // lesen

Es ist auch noch möglich, das Objekt wie ein Array anzusprechen, dazu dann aber mehr in einem der nächsten Artikel, wenn wir das Interface ArrayAccess implementieren.

Die Methoden __isset() und __unset() sind vom Namen her ja eigentlich schon selbsterklärend. Mit __isset() lässt sich prüfen, ob eine Eigenschaft gesetzt ist oder auch nicht.

echo isset($model->id) ? 'ID gesetzt' : ID nicht gesetzt';

Die eigentliche Prüfung haben wir ja bereits implementiert und müssen nun nur noch weiterleiten.

public function __isset($key)
{
  return $this->keyExists($key);
}

Nun könnte durch unset() auch eine Eigenschaft gelöscht werden. Da wir aber eine feste Struktur haben und auch behalten wollen, behandeln wir den Aufruf nicht. Wenn man es ganz streng mag, dann könnte man hier auch eine Exception mit entsprechender Nachricht werfen oder auch einfach nur einen Eintrag ins Log schreiben.

Nachdem wir bislang das meiste durch Delegation auf bestehende Methoden lösen konnten, erweitern wir das Model nun um ein paar neue Methoden.

Die vorerst letzte, noch fehlende magische Methode ist __toString(), durch die das Objekt in einen String konvertiert und z.B. mit echo ausgegeben werden kann. Nur das alleine wäre mir aber zu wenig, ich will das Model in einem vom Benutzer definierten Format ausgeben lassen.

Zuerst brauchen wir outputFormat(), um das Standardformat einstellen zu können.

public function outputFormat($format = null)
{
  $oldFormat = $this->_outputFormat;

  if(is_string($format)) {
    $this->_outputFormat = $format;
  }

  return $oldFormat;
}

Nun kann eine erbende Klasse ihr Ausgabeformat z.B. in _init() setzen. Die Methode gibt das aktuelle Format zurück und ändert es nur, wenn Format angegeben wird.

Die Formatierung der Daten wird dann, wenig überraschend, in format() gemacht. Wer mal mit Java programmiert hat, der wird diese Möglichkeit sicherlich kennen und schätzen.

public function format($format = null)
{
  $format = is_string($format) ? $format : $this->_outputFormat;

  foreach($this->keys() as $key) {
    $formatKey = '%' . strtoupper($key) . '%';
    $format = str_replace($formatKey, $this->_get($key), $format);
  }

  return $format;
}

format() nutzt das Standardformat, wenn beim Aufruf kein anderes Format angegeben wird.

Zum Schluß müssen wir format() noch über __toString() aufrufen und unser Model kann als String ausgegeben werden.

public function __toString()
{
  return $this->format();
}

In der Anwendung könnte das dann z.B. so aussehen:

<?php
class Person extends Custom_Model
{
  protected $_id;
  protected $_firstname;
  protected $_lastname;

  protected function _init()
  {
    $this->outputFormat('%ID% - %FIRSTNAME% %LASTNAME%');
  }
}

$person = new Person();
$person->setId(1)
       ->setFirstname('Bart')
       ->setLastname('Simpson');
echo $person;  // Ausgabe: 1 - Bart Simpson

Hier lässt sich dann auch schon gut erkennen, wie übersichtlich die konkreten Klassen sein können, obwohl sie schon relativ viel bieten. Im nächsten Artikel implementieren ich dann das Interface ArrayAccess.

Ein Kommentar

  1. Pingback: Es ist ein Model und es sieht gut aus « ebene7

Hinterlasse eine Antwort

Pflichtfelder sind mit * markiert.


Schlagwörter: A/B-Test, AbstractType, Adapter, AddOn, Administration, Ajax, Amazon, Animation, Annotations, Anonyme Klasse, Ant, Apache, API, Array, ArrayAccess, Attachment, Auftrag, Ausbildung, Auswertung, Authentifizierung, AutoLoader, AWS, Bedienung, Bedingung, Benchmark, Berechtigung, Berlin, Bildbearbeitung, Bildschirmfoto, Blog, Blogroll, BOM, Bootstrap, Bot, Browser, Bugtracker, Byte Order Mark, Bücher, Cache, CakePHP, Call-Center, Callback, CamelCase, Canvas, Captcha, CDN, Cheatsheet, CLI, Clickout, Closure, Cloud, CodeSniffer, Collection, Community, Comparator, Config, Contest, Controller, Converter, CouchDB, Countable, Cronjob, CRUD, CSS, CSV, CustomLibrary, Custom_Model, Daemon, Data Mapper, Datei, Datenbank, Datenstruktur, Datentypen, Dating, Datum, Debug, Decorator, Dekorierer, Design, Design Patterns, Doctrine, Dokumentation, Dump, Duplikat, each, EC2, Eclipse, Email, Entwicklung, Entwurfsmuster, Enum, Erweiterung, Event, Eventhandling, Exception-Handling, Extension, Facebook, Factory, Fallback, Fehler, Fehlermeldung, Filter, Firefox, Flash, flexigrid, Foreach, Formatierung, Formular, Framework, FTP, Funktion, Futon, ga:pi(), Getter, Google Analytics, Hash, Hash-Bang, Header, htaccess, HTML5, htpasswd, HTTP, HTTPS, IDE, If, Implementierung, InnoDB, Interceptor, Interface, Internet Explorer, isset, Iterator, Java, JavaScript, Job, jQuery, Kommentar, Konfiguration, Konsole, Kontrollstruktur, kopieren, kostenlos, Kundenbetreuung, Late Static Binding, Layout, Links, Linux, Listeners, Lizenz, Logging, Löschen, Magento, Magic Methods, Manual, ManyToMany, Marketing, Methode, Model, Monolog, MVC, MySQL, NetBeans, Network, Nirvanix, Objekt, Observable, Observer, OneToMany, Online Tool, OOP, Open Source, Operator, OR-Mapper, Order, ORM, O’Reilly, Parameter, Partnersuche, Passwort, Performance, PHP, php.ini, PHP hates me, phpMyAdmin, PHPUnit, Plugin, Popup, Proxy, Prüfsumme, Prüfung, QR-Code, Qualitätssicherung, Query, Queue, Redesign, Refactoring, Reflection, Request, Response, Responsive Design, Rest-API, Rockstar, Rollback, Routing, S3, Samba, Scheifen, Schleife, Schutz, Screenshot, Secure Shell, Selbstreferenz, Server, Setter, setTimeout, Shop, Sicherheit, Sicherung, Sichtbarkeit, Singleton Pattern, Skin, SOAP, Social Network, Software, Sortierung, Sourcecode, Spam, Speicherproblem, Spickzettel, SPL, Splittest, SSH, SSL, Stammtisch, Statement, static, Statistik, Status, Stellvertreter, Strategy Pattern, Stream, String, Stuttgart, Stylesheet, Subversion, Sun VirtualBox, Support, SVN, Switch, Symfony, Symfony2, Symfony Live, Tag, Template, Template Method, Ternär Operator, Testing, Theme, Thumbnail, Tool, Tour, Tracking, Twig, Twitter, Type-Cast, Ubuntu, Umwandlung, Underscore, unset, Update, Upload, Url, User Story, Validierung, Vererbung, Versionskontrolle, Versionsnummer, Verzweigung, Video, Videospiel, Virtualisierung, Visitor Pattern, Vorschaubild, walk, Warteschlange, Webserver, Webservice, Weiterleitung, Werkzeug, Windows, WindowsAzure, WordPress, Wrapper, Writer, XML, Youtube, Zeitschleife, Zeitsteuerung, Zend Framework, Zend_Application, Zend_Cloud, Zend_CodeGenerator, Zend_Http_Client, Zend_Reflection, Zend_Service, ZPress, Zugangskontrolle, Zugriffsmethode