copyright (c)  2003   useGroup
PHP

Jochen Stärk
PHP-Tutorial
Objektorientierung in PHP 5

nach untenSinn von Objektorientierung
nach untenZugriffskontrolle auf Methoden und Eigenschaften
nach untenAbstrakte Klassen
nach untenInterfaces
nach untenObjekte kopieren
nach untenExceptions

Sinn von Objektorientierung

nach obenSinn von Objektorientierung 

Der Sinn von Objektorientierung ist es nicht, schnellere Programme zu erstellen, sondern deren Entwicklung zu beschleunigen und vor allem deren Erweiterbarkeit zu gewährleisten.

Vererbung ist dabei ein wichtiger Punkt, und abstrakte Klassen sind eine wundervolle Möglichkeit, einen generellen Ansatz zu programmieren, der dann von vielen Erben auf die Bedrfnisse angepasst wird. Das, und zum Beispiel Zugriffsschutz, wurde von PHP 4 noch nicht untersttzt. Das heißt nicht, dass es nicht ging, zum Beispiel so etwas wie eine abtrakte Klasse zu machen, von der eine Methode von einem Erben berschrieben wurde, aber PHP hat zum Beispiel nicht gewarnt, wenn ein Erbe es vergessen hat, diese Methode zu berschreiben.

Zum Glück wird mit PHP 5 vieles von der Sprache unterstützt. So entfällt zum Beispiel auch das umständliche und oft fehlerträchtige herumgekrame mit Referenzen. Objekte werden in PHP 5, wie in Java auch, ganz einfach nicht mehr kopiert, sondern referenziert.


Zugriffskontrolle auf Methoden und Eigenschaften

nach obenZugriffskontrolle auf Methoden und Eigenschaften 

Als eines der nebenwirkungsfreiesten Konzepte in der Computerwelt gilt das Blackbox-Prinzip, nach dem eine Klasse nicht wissen muss, wie die andere funktioniert. Um zu verhindern, dass zum Beispiel ein anderer Programmierer auf Funktionen oder Eigenschaften zugreift, die die interne Arbeitsweise der Klasse betreffen, kann man den Zugriff auf Funktionen beschränken, die der Klasse angehören (private).

<?

class SearchResult
{
  private $numResults=0;
  private $resArray;

  public function search($query)
  {
    $this->resArray=array("eis","wasser","dampf");
    $this->numResults=count($this->resArray);


  }
  public function getResult()
  {
    $res="";
    for ($resIndex=0; $resIndex<$this->numResults; $resIndex++)
    {
      $res.=$this->resArray[$resIndex];
    }
    return $res;
  }

  public function getNumResults()
  {
    return $this->numResults;
  }
}

$mySearchResult=new SearchResult();
$mySearchResult->search("nach einer guten Idee");
echo $mySearchResult->numResults;  //Fehler: Cannot access private property
$mySearchResult->numResults=5;
// Hier auch. Ihn das Ändern zu lassen wäre gefährlich, weil getResult
// Fehler machen wrde. Der Programmierer kann getNumResults() verwenden.
echo $mySearchResult->getResult();

?>
In diesem Beispiel sind die Methoden public, und die properties private. Obwohl es nichts gegen private Methoden einzuwenden gäbe, gibt es einige Grnde gegen öffentliche Properties:

Nebenbei gibt es noch weitere Schlsselwörter: Neben public und private ist mein Favorit "protected", das der eigenen Klasse, aber auch deren Erben den Zugriff gestattet.


Abstrakte Klassen

nach obenAbstrakte Klassen 

Abstrakte Klassen werden nicht instanziiert, sie dienen nur dazu, gemeinsame Methoden und Eigenschaften zu definieren, die später von einer Anzahl ähnlicher Erben verwendet werden.

Bei vielen Dinge, die klassifiziert werden können, könnte man die Klassifizierung als abstrakte Klasse verwenden. Elefanten, Pinguine und Menschen sind Tiere, es gibt aber kein Tier, das einfach nur ein Tier wäre und keiner Gattung angehören wrde.

Abstrakte Klassen sind deswegen so schön, weil sie die Dinge, die die Erben unterscheiden, gar nicht implementieren, sondern mehr oder weniger sagen können: Diese Methode muss jeder einzelne meiner Erben für sich implementieren, ich kann aber nicht sagen, wie das bei meinen Erben im einzelnen aussieht.

<?

abstract class cAbstractAnimal
{

  function getNumberFeets()
  {
    return 4;
  }

  abstract function getCommunicationMethod();

}

class cHuman extends cAbstractAnimal
{
  function getNumberFeets()
  // hier wird eine Funktion berschrieben, obwohl sie nicht abstrakt ist. Das
  // hätte man auch in einer normalen Klasse gekonnt.
  {
    return 2;
  }
  function getCommunicationMethod()
  {
  // Wenn diese Funktion in dieser Klasse NICHT deklariert wird, kommt der Fehler
  // Class chuman contains 1 abstract methods and must therefore be declared abstract
    return "sprechen";

  }
}

class cDog extends cAbstractAnimal
{
  function getCommunicationMethod()
  {
  // Wenn diese Funktion in dieser Klasse NICHT deklariert wird, kommt der Fehler
  // Class chuman contains 1 abstract methods and must therefore be declared abstract
    return "bellen";

  }
}

  $human=new cHuman();
  echo "Menschen haben ".$human->getNumberFeets()." Füße und ".$human->getCommunicationMethod().".<br>";
  $dog=new cDog();
  echo "Hunde haben ".$dog->getNumberFeets()." Füße und ".$dog->getCommunicationMethod().".<br>";

?>
Das Beispiel ist noch nicht so prickelnd, weil abstrakte Klassen desto mehr Arbeit sparen, desto mehr nicht-abstrakte Funktionalität in ihnen deklariert ist und desto mehr Erben sie haben, die sich in möglichst wenigen (in der Ursprungsklasse abstrakten) Funktionen unterscheiden. Bei 50% abstrakter Funktionen und nur zwei Erben spielen sie also noch nicht wirklich ihre Stärken aus.


Interfaces

nach obenInterfaces 

Ein ziemlich extremer Ansatz von abstrakten Funktionen sind Interfaces. In Interfaces sind alle Funktionen abstrakt. Wie Java ist PHP eine Sprache, die keine Mehrfachvererbung unterstützt, und wie bei Java wird dieser Grundsatz für Interfaces aufgeweicht. Eine Klasse kann von eine Vaterklasse und einer beliebige Anzahl von Interfaces erben.

Warum mehrfachvererbung problematisch ist erklärt folgendes Beispiel: Sowohl rectangle als auch triangle erben von Klasse shape und überschreiben bei dieser Gelegenheit die Methode getArea. Kommt man jetzt auf die Idee ein Rechteck mit einem aufgesetzten Dreieck als Erbe beider Klassen definieren zu wollen müsste man für dessen Methode getArea entweder festlegen, welche der Vaterklassen benutzt werden soll oder eine Warnung einbauen dass in diesem Fall getArea in der Klasse RectangleWithTriangle neu implementiert werden muss. Beide Vatermethoden aufzurufen kommt im Grunde nicht in Frage, weil die Reihenfolge des Aufrufs für das Ergebnis entscheidend sein kann und man nicht wüsste was man mit eventuellen Return-Werten machen sollte.

In Java werden Interfaces zum Beispiel gerne bei Funktionen benutzt, die einen Benutzerzugriff erlauben. So kann in einem Interface zum Beispiel eine virtuelle Funktion liegen, die die Behandlung eines Tastendrucks definiert. Man kann dann von bestimmten Klassen erwarten, dass sie bestimmte Interfaces implementieren, und aus der Basisklasse die entsprechenden virtuellen Funktionen aufrufen.

Im hart typisierten Java macht das Sinn, beim schwach typisierten PHP, bei dem man höchstens durch Hints eine gewisse Klasse (zur Laufzeit) erwarten darf, ist das problematisch. Ein Interface ist also so etwas wie ein Satz von abstrakten Funktionen.

Das Originalbeispiel aus der Zend 2-Ankndigung

interface Throwable {
   public function getMessage();
}

class MyException implements Throwable {
   public function getMessage() {
       // ...
   }
}


Objekte kopieren

nach obenObjekte kopieren 

Da PHP ab der Version 5 bei Objekten mit Referenzen arbeitet, muss man auch irgendwie in der Lage sein, explizit eine Kopie eines Objekts herzustellen:

<?
class cAngestellter
{
  public $wohnort; // Ausnahmsweise public-Variablen, die sind hier unabhängig, unkritisch
  // und ansonsten zuviel Schreibarbeit.
  public $ausbildung;
  public $name;

  function __toString() {
       return "<br>".$this->name." hat ".$this->ausbildung." und wohnt in ".$this->wohnort;
   }


}


$zwilling1=new cAngestellter();
$zwilling1->wohnort="Berlin";
$zwilling1->ausbildung="allgemeine Hochschulreife";
$zwilling1->name="Bernd Bleifuß";
$zwilling2=$zwilling1->__clone();
// bei $zwilling2=$zwilling1; hießen beide gleich Benjamin Bleifuß $zwilling2->name="Benjamin Bleifuß";


  echo $zwilling1;
  echo $zwilling2;

?>


Ganz nebenbei zeigt das Beispiel auch noch eine schönee Möglichkeit, Objekte zu konvertieren. Zumindest in Strings, mit einer "überladenen" (also berschreibenen) __toString-Funktion, deren Rückgabewert automatisch verwendet wird, wenn das Objekt als ganzes in einen String konvertiert werden soll.


Exceptions

nach obenExceptions 

Exceptions sind Error-Codes im objektorientiertem Paradigma, sie sind besonders dort sinnvoll wo

Erstmal wichtig zu wissen: exceptions werden mit throw geworfen und mit catch gefangen. Sollte etwas unabhängig von einer Exception ausgeführt (zum Beispiel Bereinigungen nach angefangener arbeit) kommt das in einen "finally" block.

Das Throw sieht dabei so aus: throw new Exception("illegal handler type");

Beispiel: Error codes mit einem Programm das eine Datei liest in der eine positive oder negative Zahl (oder 0) enthalten ist:


$fh=null;
If (seeIfFileExists($f)===true) {
if (hasFileAccess($f)===true) {
$fh=fopen($f);
if ($fh) {
if (fileSize($fh)>0) {
$fcont=readfile($fh);
if ($fcont!==null) {
$balance=analyzeContentAndGetTotalSum($fcont);
if ($balance<0) {
		echo "Account is negative";
		} else if ($balance===0) {
		echo "Account is 0";
		} else if ($balance>0) { 
	echo "Account is positive";
	} else if ($balance===null) { 
	fclose($fh);
	die("don't know what happened, no balance returned, maybe file content not numeric");
	
	}	
	
	} else { fclose($fh); die("could not read file content"); }
	
	} else { fclose($fh); die("something wrong with file: <=0 file size reported"); }
								  } else die ("could not open file");
								  
								  } else die("have no access privileges");
								  } else die("file does not exist");

Das gleiche Beispiel könnte mit Exceptions so aussehen:	
$fh=null;
  try {
	  seeIfFileExists($f);
	  hasFileAccess($f);
	  $fh=fopen($f);
	  $fcont=readfile($fh);
	  $balance=analyzeContentAndGetTotalSum($fcont);
	  if ($balance<0) {
		echo "Account is negative";
	  } else if ($balance===0) {
		echo "Account is 0";
	  } else if ($balance>0) { 
	        echo "Account is positive";
	  } 
	} catch (Exception $e) {
	  echo $e->getMessage();
	}
  finally {
	  if ($fh!==null) {
		  fclose($fh);
	  }
  }
Wenn die Funktionen anständige exceptions throwen, sprich verschiedene exceptionklassen benutzen, kann man auch leicht auf den Art des Vorfalls reagieren indem man das catch (Exception $e) ersetzt durch
  
   } catch (fileContentException $e) {
	  blameFileAuthor();
	  echo $e->getMessage();
   } catch (fileReadException $e) {
	  blameFileAuthor();
	  echo $e->getMessage();
   } catch (fileSizeException $e) {
	  blameFileAuthor();
	  echo $e->getMessage();
  } catch (fileOpenException $e) {
	  blameOperatingSystem();
	  echo $e->getMessage();
  } catch (fileAccessException $e) {
	  blameOperatingSystem();
	  echo $e->getMessage();
  } catch (fileExistenceException $e) {
	  blameUserForSelectingWrongFile();
	  echo $e->getMessage();
  }

Wie man leicht sieht, wären hier verschiedene Kategorien von Fehlern sinnvoll, was man implementieren kann, indem man die Exception-Klasse vererbt: mit einer Klassenhierarchie wie

  	    Exception->AuthorException->fileContentException
		Exception->AuthorException->fileReadException 
		Exception->AuthorException->fileSizeException 
		Exception->OSException->fileOpenException 
		Exception->OSException->fileAccessException 
  
kann man den code nämlich kürzen in
	
  } catch (AuthorException $e) {
	blameFileAuthor();
	echo $e->getMessage();
  }  catch (OSException $e) {
	  blameOperatingSystem();
	  echo $e->getMessage();
  }  catch (fileExistenceException $e) {
	  blameUserForSelectingWrongFile();
	  echo $e->getMessage();
}
 
Noch ein paar Bemerkungen zu Vorteilen von Exceptions:
  1.  
    } catch (fileContentException $e) {
    blameFileAuthor();
    echo $e->getMessage();
      }
     
    nett zu erwähnen dass, da die fileContentException in der funktion gethrowet wird wo sie festgestellt wird, auchaus auch detailliertere angaben enthalten kann als "don't know what happened, no balance returned, maybe file content not numeric".

    Die Funktion könnte zum Beispiel so aussehen:

      
      Protected function analyzeContentAndGetTotalSum($fileContent) {
      if (!ctype_numeric($fileContent)) throw new fileContentException("file content is not numeric: $fileContent");
      $res=intval($fileContent);
      // the following two thrown exceptions are per se probably more assertions
      if ($res>1000000) throw new fileContentException("you are too rich for this program: $res");
      if ($res<1000000) throw new fileContentException("you are too poor for this program: $res");
    return $res;
    }
    

  2. Exception-Subklassen kann man prima verwenden um event-handler-code einzubauen, zum Beispiel
    Class AuthorException extends Exception {
    	public function __construct($message) {
    	mail ("author@usegroup.de", "exception", "Your program is buggy because some idiot managed to write a file that you can't parse:".$message->getText());
    	super::__construct($message);
    }
    }
    
    Class OSException extends Exception {
    	public function __construct($message) {
    	mail ("linus.torvalds@osdl.org", "exception in linux", "Fix this:".$message->getText());
    	super::__construct($message);
    }
    


copyright (c)  2003   useGroup