Alphabetische Paginierung aus Array: die Tücken des PHP-sort()

Eigentlich hats mich ja nur interessiert, ob das recht aufwendig ist, aber das hielt sich in Grenzen, deswegen mach ich es noch der Vollständigkeit halber. Voraussetzung ist wie im letzten Beitrag, dass die auszugebenden Daten in Form eines Arrays vorliegen, ich nehme wieder meine 334 Kochrezepte als JSON, so wie im letzten Beitrag zur nummerischen Pagination. Nach dem json_decode sieht die Struktur des Arrays wie gehabt wie folgt aus:

array(1) 
{ ["rezepte"]=> array(334) {
 [0]=> array(3) { ["ID"]=> string(4) "1828" ["post_title"]=> string(31) "Pudding nach Art des Hauses Leu" ["post_content"]=> string(37) "Vanille oder Schoko oder Johannisbeer" }
 [1]=> array(3) { ["ID"]=> string(4) "1827" ["post_title"]=> string(5) "Milch" ["post_content"]=> string(23) "reicht nicht bis morgen" } 
 [2]=> array(3) { ["ID"]=> string(4) "1825" ["post_title"]=> string(14) "Petersilwurzel" ["post_content"]=> string(29) "hab ich abgeerntet, ist nicht" }

Jetzt interessieren mich aber eigentlich nur die post_title, die krieg ich mit:

$akt_titel = $dataObject['rezepte'][$i]['post_title'];

Und die Titel der Rezepte möchte ich nach Buchstaben seitenweise sortiert ausgeben. Also, pack’mas.

Das Formular mit den Buttons

Dafür fülle ich mir erst einmal ein Array mit den Buchstaben a-z, daraus baue ich dann das Formular:

//Buchstaben a-z in Array schreiben
$letters = array();
for ($i = 'a', $j = 1; $j <= 26; $i++, $j++) {
    $letters[$j] = $i;    
}
//Formular mit Buttons a-z erzeugen
echo "<form action = '#' method = 'post'>";

for ($i=1; $i <=26; $i++){
    echo "<input type='submit' id='el_button' name='".$letters[$i]."' value='".$letters[$i]."'>";
}
    echo "</form>";

Das sieht jetzt erstmal so aus:

alfa_buttons

alfa_buttons

Dann ermittle ich, welcher Buchstabe angeklickt wurde, und rufe mit diesem Buchstaben als Parameter meine Ausgabefunktion auf:

//Ermitteln welcher Buchstabe angeklickt wurde und Ausgabefunktion aufrufen
for ($j = 1; $j <= 26; $j++){    
        if (isset($_POST[''.$letters[$j].''])){
            
            return el_aufruf("".$letters[$j]."");
        }
        
    }

In der Ausgabefunktion hole ich mir mein Array mit allen Daten, steppe es ganz durch und picke mir mit dem if nur die Einträge heraus, wo der post_title mit dem als Parameter übergebenen Buchstaben anfängt. Diese werden mit array_push einer nach dem anderen an mein Hilfsarray $ausgabe angehängt.

function el_aufruf($stabe){
            
    //JSON alle Rezepte abholen und in Array umwandeln
    $json = file_get_contents('http://localhost/kleine_api/rezepte/rezeptelesen.php');
    $dataObject = json_decode($json,true);
    $anzahl = count($dataObject['rezepte']);
    echo "Rezepte insgesamt = ".$anzahl."<br>";
            
    //Hilfsarray für Ausgabe     
    $ausgabe = array();
        
        for ($i=0; $i<$anzahl; $i++){
            //Ersten Buchstaben vergleichen
            if (strtoupper(substr(($dataObject['rezepte'][$i]['post_title']), 0, 1))== strtoupper($stabe)){
                //Hilfsarray mit Titeln zum aktuellen Buchstaben füllen
                array_push($ausgabe, $dataObject['rezepte'][$i]['post_title']);
            }
        }

Das Hilfsarray sortiere ich mir noch mit dem einfachen sort() und gebe es zeilenweise aus:

//Array sortieren und ausgeben
    sort($ausgabe);
    foreach($ausgabe as $out){
        echo $out."<br>";
    }
} //end function el_aufruf

Die Ausgabe sieht dann beispielsweise so aus:

buchstabe_c

buchstabe_c

Kleiner Pferdefuß: die Umlauts und Sonderzeichen

Der PHP sort() ist nicht dazu zu bewegen, die deutschen Umlaute anderswo als am Ende einer Liste nach dem Buchstaben Z einzusortieren. Auch Sonderzeichen wie z.B. mit Accents (Béchamel) landen gnadenlos am Ende. Das sieht man ganz deutlich am Ende der Liste zum Buchstaben B:

buchstabe_b

buchstabe_b

Da müsste man jetzt hingehen und eine eigene sort-Funktion schreiben, die aus den deutschen Umlauts Diphtonge mach (ä=ae, ö=oe…) und erst danach sortieren, vor der Ausgabe aber die Umlaute wieder einsetzen. Ach nö Leute, das ist mir echt zu stressig. Ich lebe mal mit der Ausgabe der Umlaute am Ende, vielleicht begegnet mir ja beim Googlen noch irgendwo eine elegantere Lösung.

Und prompt hab ich was gefunden: Sortierung der Umlauts wie im Telefonbuch

Auf der Webseite von Marco Krings habe ich folgende interessante Sortierfunktion gefunden:

http://www.marcokrings.de/arrays-sortieren-mit-umlauten/

Er arbeitet mit zwei Arrays für die Umlaute und die entsprechenden Diphtonge. Diese Arrays könnte man jetzt noch um die Accent-Zeichen wie z.B. é ergänzen, ich hab das mal ausprobiert:

$aSearch   = array("Ä","ä","Ö","ö","Ü","ü","ß","-","é");
    $aReplace  = array("Ae","ae","Oe","oe","Ue","ue","ss"," ","e");

Funktioniert eins a!

Meine Ausgabe wird jetzt noch um Marcos Funktion ArraySort() erweitert:

//Array sortieren und ausgeben
    //sort($ausgabe);
    $umlauts = ArraySort($ausgabe);
    foreach($umlauts as $out){
        echo $out."<br>";
    }

Jetzt paßt die Sache. die Umlaute und das é werden richtig einsortiert:

e_accent

e_accent

Das ist ausbaufähig! Vielen Dank an Marco für die geniale Vorlage.

Nachtrag: woher nehm ich jetzt die ID?

Ich habe in meiner alfabetischen Ausgabe bis jetzt nur die Titel der Rezepte verarbeitet, das reicht aber unter Umständen nicht. Für manche Zwecke wäre es schick, wenn man auch noch die ID mitnehmen könnte, zum Beispiel in WordPress für die Ausgabe des Permalinks. Man könnte jetzt auf die Idee kommen, in der for-Schleife den array_push umzustricken, so dass ein mehrdimensionales Array mit Titel und ID aufgebaut wird… ja Pustekuchen, dann fällt unser schöner Sortieralgorithmus auf die Nase, das geht leider nicht. Ich hab mir da eine quick&dirty-Lösung einfallen lassen: ich hänge die ID einfach an den Titel mit dran, und setze dazwischen als Trennzeichen ein #. Kann ich so machen, weil in meinen Rezepttiteln das Zeichen # sonst garantiert nirgendwo vorkommt. Die for-Schleife sieht dann so aus:

for ($i=0; $i<$anzahl; $i++){
  //Ersten Buchstaben vergleichen
if (strtoupper(substr(($dataObject['rezepte'][$i]['post_title']), 0, 1))== strtoupper($stabe)){
//Hilfsarray mit Titeln und ID zum aktuellen Buchstaben füllen (Trennzeichen#)
array_push($ausgabe, $dataObject['rezepte'][$i]['post_title']."#".$dataObject['rezepte'][$i]['ID']);
   }
}

Dann sieht die Ausgabe erstmal so aus:

id_angehaengt

id_angehaengt

Das zerpflücke ich mir jetzt noch in die Teilstrings vor und nach dem #, gebe nur den Titel aus und kann dann mit der ID noch was anderes anfangen. Die Ausgabeschleife sieht dann so aus:

foreach($umlauts as $out){
        
        //alle Zeichen vor dem # = Titel
        $hilf_titel = substr($out, 0, strpos($out, '#', 0));
        echo $hilf_titel."<br>";
        
        //alle Zeichen nach dem # = ID
        $hilf_id = substr(strrchr($out, "#"), 1);
        //hier kann man mit der ID weiterarbeiten
        }

Wie gesagt, das ist ein wenig quick&dirty, aber es funktioniert, und ich kann den schicken Sortieralgorithmus von Marco einwandfrei weiterverwenden.