if, else und Ternary Operator

Über if stolpert wohl jeder und so einfach die Anweisung auch ist, angewendet gibt es unzählige Schreibweisen. Der einfachste Fall wäre, tue etwas wenn und wenn dieses Etwas lediglich eine Anweisung ist, kann auf die geschweiften Klammern verzichtet werden. Dies trifft ebenso auf den sonst-Fall zu.

$test = false;

if ($test) {
  $text = 'ja';
} else {
  $text = 'nein';
}
echo $text; //nein

Dies ist identisch mit folgenden Code:

$test = false;

if ($test) $text = 'ja';
else $text = 'nein';
echo $text; //nein

Auf else kann dabei ebenfalls verzichtet werden, im Zweifelsfalle wird lediglich der Variable $text zweimal ein Wert zugewiesen, was in den meisten Fällen keine nennenswerte Auswirkung auf die Scriptlaufzeit hat.

$test = false;

$text = 'nein';
if ($test) $text = 'ja';
echo $text; //nein 

Mittels Ternary Operator lässt sich dies auch wie folgt ausdrücken:

$test = false;

$text = $test ? 'ja' : 'nein';
echo $text; //nein 

Dabei muss nicht zwangsweise eine Zuweisung direkt erfolgen, es kann z.B. auch auf Funktionen verwiesen werden.

$test = false;

function eins(){echo 'ja';}
function zwei(){echo 'nein';}

$test ? eins(): zwei();

Abschließend noch ein Beispiel ganz 'ohne' if und Co. Ist $_GET['page'] nicht definiert so hat $site den Wert 'home', sonst den von $_GET['page'].

$site = 'home';
!isset($_GET['page']) || $site = $_GET['page'];

Loop - Schleifen

Die For-Schleife dürfte die bekannteste aber keinesfalls die einfachste Schleife sein. Die normale For-Schleife besteht aus Header und Body, wobei der Body die Anweisungen in geschweiften Klammern umschließt die pro Durchlauf ausgeführt werden. Der Header enthält in runden Klammern drei Blöcke, wobei der erste Block einmalig vor der eigentliche Schleife ausgeführt wird und in der Regel den Schleifenzähler nebst seinen Startwert definiert. Der zweite Block definieret die Abbruchbedingung und wird vor jedem Durchlauf der Schleife geprüft, der letzte Block wird immer erst nach einem Schleifendurchlauf ausgeführt und enthält in der Regel eine Anweisung zum hoch- oder runterzählen des Schleifenzählers. Soweit die Theorie und anbei gleich ein Beispiel.

$arr = ['Apfel','Birne','Kirsche','Pflaume'];
for( $i=0; $i<count($arr) ;$i++ ) {
  echo $arr[$i].'<br>';
}

Das Beispiel gibt untereinander die Früchte aus dem Array aus. Wunderbar, das folgende Beispiel tut dies prinzipiell auch und darüberhinaus gibt es noch weitere Texte über die drei print-Befehle im Schleifen-Header aus. Wo aber wird nun der Schleifenzähler, die Abbruchbedingung und die Zählvorschrift definiert? Funktioniert das überhaupt? Ja, es funktioniert und ist auch syntaktisch korrekt, wenngleich niemand so programmieren würde. Die Zählvariable wird vor der Schleife definiert, die Herhöhung des Zählers erfolgt im Body der Schleife, ebenso wird dort die Abbruchbedingung geprüft und die Schleife mittels Break beendet.

$arr = ['Apfel','Birne','Kirsche','Pflaume'];
$i=0;
for( print 'Anfang<br>'; print 'Prüfen, '; print ', Zählen<br>' ) {
  if($i>=count($arr)) break;
  echo $arr[$i];
  $i++;
}

Die Ausgabe sieht wie folgt aus:
Anfang
Prüfen, Apfel, Zählen
Prüfen, Birne, Zählen
Prüfen, Kirsche, Zählen
Prüfen, Pflaume, Zählen
Prüfen,

Die print-Befehle spiegeln sehr gut die Funktionsweise der for-Schleife wieder, werden sie weggelassen, stimmt die Augabe mit dem ersten Beispiel überein, obgleich der Header fast leer ist. Eventuell kommt die Idee auf, den Header gleich ganz wegzulassen, aber die Semikolons müssen bleiben.

$arr = ['Apfel','Birne','Kirsche','Pflaume'];
$i=0;
for(;;) {
  if($i>=count($arr)) break;
  echo $arr[$i].'<br>';
  $i++;
}

Damit nicht genug, im Gegensatz zum Header kann der Schleifen-Body komplett weggelassen werden. Wozu dann noch eine Schleife, wenn nichts innerhalb der Schleife ausgeführt wird? Dann passiert doch nichts! Dem ist natürlich nicht so, wie bereits im vorhergehenden Beispiel gezeigt wurde, denn Block zwei und Block drei des Schleifen-Body's werden natürlich weiterhin pro Durchlauf ausgeführt.

Hier werden die Früchte in umgekehrter Reihenfolge ausgegeben, wobei im Block zwei die Abbruchbedingung geprüft und zuvor der Zähler gleichzeitig um eins veringert wird. Im Block drei steht die Ausgabe. Wichtig ist das Semikolon am Ende der Schleife, sonst wird die nächste Code-Zeile als Schleifen-Body interpretiert.

$arr = ['Apfel','Birne','Kirsche','Pflaume'];
for( $i=count($arr); $i-->0; print $arr[$i].'<br>' );

Eine weitere Möglichkeit ist die Ausgabe und das veringern des Zählers im Block drei durch ein Komma getrennt unterzubringen. Auch dies funktioniert.

$arr = ['Apfel','Birne','Kirsche','Pflaume'];
for( $i=count($arr); $i>0; $i--, print $arr[$i].'<br>' );

Abschließend noch ein praxisrelevantes Beispiel mit zwei Zählern, wobei Zähler $k durch bitweises Verschieben fortlaufen ganzzahlig durch 2 geteilt wird.

$arr = ['Äpfel','Birnen','Kirschen','Pflaumen'];
for( $i=0, $k=27; $i<count($arr); $i++, $k>>=1 ) {
  echo $k.' '.$arr[$i].'<br>';
}
/* Ergibt:
 * 27 Äpfel
 * 13 Birnen
 * 6 Kirschen
 * 3 Pflaumen
 * */

Neben der for-Schleife gib es auch die foreach-Scheife die für das vorliegende Beispiel, der Ausgabe eines Arrays, am besten geeignet ist.

$arr = ['Apfel','Birne','Kirsche','Pflaume'];
foreach($arr as $item){
  echo $item.'<br>';
}

Wird dabei auch der Index/Zähler benötigt, wird oft einen separater Zähler eingebaut, dabei gibt es eine wesentlich bessere Möglichkeit.

$arr = ['Apfel','Birne','Kirsche','Pflaume'];
foreach($arr as $i => $item){
  echo '['.$i.'] '.$item.'<br>';
}
/* Ergibt:
 * [0] Apfel
 * [1] Birne
 * [2] Kirsche
 * [3] Pflaume
 * */

Der Zähler ist dabei eigentlich ein 'Name/Key', der im obigen Beispiel nur nicht expiziet angeben wurde und daher automatisch zugewiesen war. Folgendes Beispiel zeigt nun die Variante, bei welcher der Key definiert wurde.

$arr = ['Mo' => 'Apfel', 'Di' => 'Birne', 'Mi' => 'Kirsche', 'Do' => 'Pflaume'];
foreach($arr as $str => $val){
  echo '['.$str.'] '.$val.'<br>';
}
/* Ergibt:
 * [Mo] Apfel
 * [Di] Birne
 * [Mi] Kirsche
 * [Do] Pflaume
 * */

Eine weitere Schleifetyp ist die while-Schleife. Sie wird vor allem dann genutzt, wenn die Anzahl der Durchläufe nicht bekannt ist, dies kann etwas bei Datenbankanfragen der Fall sein oder beim Einlesen großer Dateien, die dann gewöhnlich stückweise eingelesen werden. Im Schleifen-Header wird lediglich die Abbruchbedingung definiert, für alles andere ist der Programmierer selbst zuständig. Um es gleich vorweg zu nehmen: die while-Schleife ist letztendlich eine reduzierte for-Schleife. Folgender Syntax ist absolut identisch.

for (; ABBRUCHBEDINGUNG ;) {
  ...
}
  
while (ABBRUCHBEDINGUNG) {
  ...
}

Im folgenden Beispiel wird der Inhalt von $file fortlaufen in 1024er Häppchen in den $buffer eingelesen bis das Dateiende erreicht wird.

$handle = fopen($file, "r");
if ($handle) {
  while (!feof($handle)) {
    $buffer .= fgetss($handle, 1024);
  }
  fclose($handle);
}

Natürlich lässt sich auch das Beispiel mit dem Früchte-Array durch eine while-Schleife umsetzen. Weiters wird angenommen, es gäbe keine Möglichkeit die Array-Größe zu ermitteln - zugebenermassen sehr konstruiert - verdeutlich jedoch sehr gut, dass es viele Wege gibt, um ein 'Problem' zu lösen.

$arr = ['Apfel','Birne','Kirsche','Pflaume'];
$i=0;
while(array_key_exists($i,$arr)) {
  echo $arr[$i++].'<br>';
}

Eine weitere Variante der while-Schleife ist die do-while-Schleife. Diese ist Fussgesteuert, sprich die Abbruchbedingung steht im Schleifen-Footer und nicht im Header, dies bedeutet, dass der Schleifen-Body mit den Anweisungen mindestens einmal durchlaufen wird und zwar unabhänging davon, ob die Abbruchbedingung bereits erfühlt ist. Im praktischen Einsatz wid dieser Schleifen-Typ eher selten eingesetzt.

$arr = ['Apfel','Birne','Kirsche','Pflaume'];
$i=0;
do {
  echo $arr[$i++].'<br>';
} while(array_key_exists($i,$arr))

Warum gibt es so viele unterschiedliche Typen von Schleifen, wenn diese doch letztlich mehr oder weniger alle das gleiche tun? Nun, die Frage ist nicht so leicht zu beantworten, in erster Linie hat jeder Programmierer so seine Vorlieben und diese Art von Schleifen haben sich mit der Zeit durchgesetzt. Alle modernen Programmiersprachen sind in dieser Hinsicht ähnlich aufgebaut und beruhen ursprünglich auf Maschienensprachen bzw. die für uns etwas besser lesbare Sprache Assembler und diese kennen Schleifen im eigentlichen Sinne nicht. Dort wird das verhasste und verbannte 'GOTO' verwendet natürlich als Assembler-Befehl JMP, bei Schleifen alternativ auch LOOP, aber auch dieser Befehl tut nichts anderes als zu einer Sprungmarke springen. Eine klassische Schleife wie sie etwa auch aus BASIC bekannt ist, um auch andere Schreibweisen aus den Anfängen der Computerzeit aufzugreifen, sieht wie folgt aus:

FOR I=0 TO 10
  ... 
NEXT

und wird zunächst in einen IF/THEN-Konstrukt umgewandelt - denn auch BASIC ist eine Hochsprache - und in Assemblersprache wie folgt umschrieben:

   MOV AX,0   // AX = 0
XX:  
   CMP AX,10  // IF AX = 10
   JE  YY     // THEN GOTO YY
   ...
   INC AX     // AX = AX + 1
   JMP XX     // GOTO XX
YY:

Sogesehen muss ich rückblickend schon etwas schmunzeln, ich liebte das GOTO unter BASIC sehr, von wegen schlechter Stil - es wurde einfach besser verpackt und ist heute immer noch der Renner, nur weiß es keiner. Übrigens, die Kommentare im obigen Code spiegeln zufällig die eingangs erwähnten Blöcke 1 bis 3 des Schleifen-Headers wieder und zwar genau in dieser Reihenfolge, die Punkte den des Schleifen-Body, wobei dieser in Assembler meist mittels CALL auf ein Unterprogramm/Funktion verweist, sofern er umfangreicher ausfällt (vergleichbar mit GOSUB in BASIC).

Suchen ohne Datenbank

Nicht immer erfordert ein Projekt ein CMS-System, auch das weitverbreitete Wordpress - ein Blog-System - ist für reine Websites überdimensioniert und zwängt einem zudem in ein festes Schema, was zwar wiederum angepasst werden kann, aber letztlich auf Dauer dazu führt, dass an zig Stellen, zig Stellschrauben entstehen, viele Plugins notwendig werden und der Content sich in der Datenbank verbirgt.

Wer dann nach zwei Jahren noch weiß wo, was, wie und vor allem warum es genau so und nicht anders war, oder Texte ohne Datenbank umschreiben möchte, oder umziehen bzw. das System wechseln muss, weiß wo die Gefahr liegt. Solange alles funktioniert, ja ... sonst Essig.

Eine klassische Webseite, ist mittels PHP schnell und einfach händelbar, bleibt stehts erweiterbar und ist weitgehend unabhängig. Jeder Content hat seine eigene Seite. Header, Footer und Kontakt bekommen ihre eigenen PHP-Seiten und werden nicht in den Ordner der Content-PHP-Seiten abgelegt. So lässt sich ausschließlich der Content-Ordner wie eine Datenbank bequem durchsuchen. Dabei muss auch nicht auf PHP im Contentbereich (z.B. zum Generieren einer Galerie) verzichtet werden. Ebenso kann eine Ergebnisseite der Suche mit Textpassagen sowie hervorgehobenen Suchwörtern erstellt und die Verlinkung mit einem Hightlighter-Parameter versehen werden.

index.php - Content einbinden

Der Content einer Seite befindet sich in einem speziellen Ordner (hier CONTENT) und wird über dem Parameter $page geladen. Ist diese Seite nicht vorhanden, wird eine Fehlermeldung zurückgegeben. Gibt es den hightlight-Parameter, werden zusätzlich ein Javascript und eine Funktion nachgeladen, die das Highlighting auf der Seite übernehmen. Der Javascript erfordert JQuery, welches bereits im Headerbereich eingebunden ist.

Wurde die Suche gestartet, so wird die search.php eingebunden und die Funktion my_search mit den Suchparametern gestartet, die hier als POST-Request vom Suchformular weitergegeben werden.


$page = isset($_GET['page']) ? $_GET['page'] : 'home';

if ($page == 'search') {
  include ('search.php');
  my_search($_POST['search']);
} else {
  if (file_exists('CONTENT/'.$page.'.php')) {
    include_once ('CONTENT/'.$page.'.php') ;
    if (isset($_GET['highlight'])){
      echo '<script src="js/jquery.highlight.js"></script>';
      echo '<script>$(function(){$("main").highlight(["'.str_replace(',','","',$_GET['highlight']).'"]);});</script>';
    }
  } else {
    echo 'Ups! - Nix da!';
  }
}

search.php

Die search.php besteht aus drei Funktionen. Die erste Funktion hebt die gefundenen Wortfragmente in den Textpassagen des Suchergebnisses hervor, und zwar so, dass die Groß- und Kleinschreibung beibehalten wird. Die Suche selbst ist nicht casesensitiv. readTxt ließt eine php Datei ohne PHP- und HTML-Tags ein. Die dritte Funktion ist die eigentliche Suche, sie besteht aus drei Teilen, dem Ermitteln aller Seiten, das Erstellen der Trefferliste und die Ausgabe des Suchergebnisses.

function highlightStr($haystack, $needle) {
  if (strlen($haystack) < 1 || strlen($needle) < 1) {
    return $haystack;
  }
  preg_match_all("/$needle+/i", $haystack, $matches);
  if (is_array($matches[0]) && count($matches[0]) >= 1) {
    foreach ($matches[0] as $match) {
      $haystack = str_replace($match, ''.$match.'', $haystack);
    }
  }
  return $haystack;
}

function readTxt($file) {
  $handle = fopen($file, "r");
  if ($handle) {
    while (!feof($handle)) {
      $buffer .= fgetss($handle, 1024);
    }
    fclose($handle);
  } 
  return $buffer;
}

function my_search($searchStr) {
  //Ermitteln der Seiten
  //Erstellen der Trefferliste
  //Ausgabe des Suchergebnisses
}

Ermitteln der Seiten

Mittels readdir wird zunächst der Inhalt des Ordners CONTENT eingelesen und die Dateinamen nach Prüfung auf die Endung php dem Array $siteName zugewiesen. Gleichzeitig wird für jede Seite eine Treffeliste $hit initialisiert.

$handle = opendir('CONTENT');
while(($file=readdir($handle)) !== false) {
  if (substr($file,-4) == '.php') {
    $siteName[] = $file;
    $hit[] = 0;
  }
}
closedir($handle);

Erstellen der Trefferliste

Zunächst wird der Suchstring $searchStr von allen nichtalphanummerischen und alleinstehenden Zeichen gesäubert und daraus ein Array mit den Suchbegriffen $searchWords erstellt. Anschließend wird für jede Seite der von PHP- und HTML-Tags befreite Content eingelesen und mit den Suchbegriffen verglichen. Bei einem Treffer, wird das Flag gesetzt, der gefundene Begriff der Liste der zutreffenden Wörter hinzugefügt und der Seitentext $text selbst abgespeichert. Weiters wird die Trefferliste binär akkumuliert, sodass später bei der Ausgabe der erste Suchbegriff die höchste Priorität erhält.

$searchStr = preg_replace('#[^\w]|\b.\b#u',' ',$searchStr);
$searchStr = preg_replace('#^\s+|\s+$|\s+(?=\s)#','',$searchStr);
$searchWords = explode(' ',$searchStr);
$flag = FALSE;

foreach ($siteName AS $i => $site) {
  $text = readTxt('CONTENT/'.$site);
  foreach ($searchWords AS $searchWord) {
    $hit[$i] <<= 1;
    if (preg_match('/'.$searchWord.'/i',$text)==1) {
      $hit[$i]++;
      $flag = TRUE;
      $sWordHits[$i][] = $searchWord;
      $sitetext[$i] = $text; 
    }
  }
}

Ausgabe des Suchergebnisses

Die erste Schleife beginnt mit dem höchsten Trefferwert und vergleicht diesen mit jeder Seite kleiner werdend, sobald ein Trefferwert mit der der Seite übereinstimmt, wird der Counter um 1 erhöht, die Position des ersten gefunden Wortes ermittelt, eine Textpassage von +/- 75 Zeichen (sofern möglich) in $sitetext abgelegt und alle vorkommenden Suchbegriffe innerhalb der Passage hervorgehoben. Danach wird die Ausgabe zusammengesetzt, beginnend mit einen Link zur Seite, der den Highlight-Parameter beinhaltet, die gefunden Wörter (Treffer) und die zuvor abgelegte Textpassage, dies könnte auch durch Ausgabearray geschehen, ich habe mich für die direkte Variante entschieden.

echo '<h2>Suchergebnis</h2>';
if ($flag) {
  $output='';
  $counter=0;
  for ($k = max($hit);$k>=1;$k--) {
    foreach ($siteName AS $i => $site) {
      if($hit[$i] == $k) {
        $counter++;
        $pos = stripos($sitetext[$i], $sWordHits[$i][0]) - 75;
        if ($pos < 0) $pos = 0;
        $sitetext[$i] = utf8_encode(substr(utf8_decode($sitetext[$i]), $pos, 150));
        $sitetext[$i] = highlightStr($sitetext[$i],implode($sWordHits[$i],'|'));
        $page = pathinfo($site,PATHINFO_FILENAME);
        $output.='<p class="search-list"><a href="?site='.$page.'&highlight='.implode($sWordHits[$i],',').'">'.ucwords(str_replace('-',' ',$page)).'</a><br /><span>Treffer: <strong>'.implode($sWordHits[$i],' ').'</strong><br />...'.$sitetext[$i].'...</span></p>';
      }
    }
  }
  echo '<p class="search-erg">Ihre Suche nach "<strong>'.$searchStr.'</strong>" ergab <strong>'.$counter.'</strong> Treffer.</p>';
  echo $output;
} else {
  echo '<p class="search-erg">Ihre Suche nach "<strong>'.$searchStr.'</strong>" hat keine Treffer zurückgegeben.</p>';
}