Wstęp
W dzisiejszym artykule omówimy sortowanie danych w PHP, przyglądając się różnym podejściom oraz optymalizacjom, które można zastosować. Zacznę od analizy przykładowego kodu, który stosowałem na początku mojej drogi Developera PHP, a następnie przejdę do omówienia dwóch zoptymalizowanych wersji tego kodu: jednej dla starszych wersji PHP i drugiej dla PHP 8 i nowszych.
Analiza kodu wyjściowego
Zacznijmy od kody wyjściowego:
<?php
setlocale(LC_ALL, 'pl_PL');
$data = [
['name' => 'Ąan', 'surname' => 'Kowalski', 'age' => 30],
['name' => 'Andrzej', 'surname' => 'Nowak', 'age' => 23],
['name' => 'Ąenon', 'surname' => 'Sikora', 'age' => 34],
['name' => 'ąndrzej', 'surname' => 'Nowak', 'age' => 23],
['name' => 'Aenon', 'surname' => 'sruba', 'age' => 45],
['name' => 'Ącki', 'surname' => 'śruba', 'age' => 34],
['name' => 'andrzej', 'surname' => 'Nowak', 'age' => 23],
['name' => 'jan', 'surname' => 'kowalski', 'age' => 34],
['name' => 'Ąndrzej', 'surname' => 'Nowak', 'age' => 46],
['name' => 'Ęcki', 'surname' => 'Zima', 'age' => 65],
['name' => 'Ącki', 'surname' => 'Sato', 'age' => 34],
];
foreach($data as $key => $rec) {
echo $key."\t=>\t".$rec['name'].', '.$rec['surname'].': '.$rec['age']."\n";
}
echo "\n\n";
sort_field($data, 'name', false);
foreach($data as $key => $rec) {
echo $key."\t=>\t".$rec['name'].', '.$rec['surname'].': '.$rec['age']."\n";
}
function sort_field(&$data, $field, $reverse)
{
$replaceCharsArray = [
'ą' => 'azzz',
'ć' => 'czzz',
'ę' => 'ezzz',
'ł' => 'lzzz',
'ń' => 'nzzz',
'ó' => 'ozzz',
'ś' => 'szzz',
'ż' => 'zxzzz',
'ź' => 'zyzzz',
'Ą' => 'Azzz',
'Ć' => 'Czzz',
'Ę' => 'Ezzz',
'Ł' => 'Lzzz',
'Ń' => 'Nzzz',
'Ó' => 'Ozzz',
'Ś' => 'Szzz',
'Ż' => 'Zxzzz',
'Ź' => 'Zyzzz'
];
foreach($data as $key => $rec) {
$data[$key]['name'] = strtr($rec['name'], $replaceCharsArray);
$data[$key]['surname'] = strtr($rec['surname'], $replaceCharsArray);
}
usort($data, function ($a, $b) use ($field, $reverse) {
$aa = iconv('UTF-8', 'ASCII//TRANSLIT', $a[$field]);
$bb = iconv('UTF-8', 'ASCII//TRANSLIT', $b[$field]);
$compare = strcasecmp($aa, $bb);
return $reverse ? -$compare : $compare;
});
foreach($data as $key => $rec) {
$data[$key]['name'] = strtr($rec['name'], array_flip($replaceCharsArray));
$data[$key]['surname'] = strtr($rec['surname'], array_flip($replaceCharsArray));
}
}W podanym kodzie mamy do czynienia z tablicą asocjacyjną $data, która przechowuje dane o osobach, takie jak imię, nazwisko i wiek. Tablice asocjacyjne, reprezentujące poszczególne osoby, zawierają klucze 'name', 'surname' i 'age'. Wartości imion i nazwisk mogą zawierać polskie znaki diakrytyczne, co może wpłynąć na proces sortowania danych.
W pierwszej części kodu, za pomocą pętli foreach, wyświetlane są wszystkie rekordy z tablicy $data wraz z indeksami.
Wstępna funkcja sortująca
Następnie, funkcja sort_field() jest wywoływana, aby posortować tablicę $data według podanego kryterium sortowania (w tym przypadku 'name’) oraz zgodnie z określonym kierunkiem sortowania (rosnąco lub malejąco). W funkcji sort_field() dzieje się kilka rzeczy:
- Zdefiniowany jest tablica
$replaceCharsArray, która służy do zamiany polskich znaków na ciągi ASCII. Dzięki temu, sortowanie będzie działało poprawnie dla polskich znaków. - Przed sortowaniem, polskie znaki w imionach i nazwiskach są zamieniane na odpowiednie ciągi ASCII za pomocą funkcji
strtr(). - Wykorzystując funkcję
usort(), tablica$datajest sortowana według wartości kolumny wskazanej przez argument$field. Do porównywania wartości używamy funkcji anonimowej, która korzysta z funkcjiiconv()do konwersji wartości na odpowiednie ciągi ASCII, a następnie porównuje je za pomocą funkcjistrcasecmp(). Kierunek sortowania zależy od wartości argumentu$reverse. Jeśli$reversema wartośćtrue, sortowanie będzie odbywać się w porządku malejącym, w przeciwnym razie sortowanie będzie rosnące. - Po posortowaniu tablicy, zamiana ciągów ASCII z powrotem na polskie znaki diakrytyczne zostaje przeprowadzona za pomocą funkcji
strtr()i przekształconej tablicy$replaceCharsArray.
Na końcu, pętla foreach wyświetla posortowane dane z tablicy $data.
Podsumowując, ten kod składa się z trzech głównych części:
- Wyświetlenie danych z tablicy
$dataprzed sortowaniem. - Sortowanie danych w tablicy
$dataza pomocą funkcjisort_field(), która uwzględnia polskie znaki diakrytyczne poprzez zamianę ich na ciągi ASCII, sortowanie za pomocą funkcjiusort()i anonimowej funkcji porównującej, a następnie przywrócenie polskich znaków diakrytycznych. - Wyświetlenie danych z tablicy
$datapo sortowaniu.
Kod pokazuje, jak można posortować tablicę z danymi zawierającymi polskie znaki diakrytyczne, biorąc pod uwagę specyfikę tych znaków w trakcie sortowania. Dzięki temu, uzyskujemy poprawne wyniki sortowania dla różnych przypadków danych wejściowych.
Refaktoryzacja kodu
Kod ma teraz taką postać:
<?php
setlocale(LC_ALL, 'pl_PL');
$data = [
['name' => 'Ąan', 'surname' => 'Kowalski', 'age' => 30],
// ...
['name' => 'Ącki', 'surname' => 'Sato', 'age' => 34],
];
display_data($data);
echo "\n\n";
sort_field($data, 'name');
display_data($data);
function display_data($data) {
foreach($data as $key => $rec) {
echo $key."\t=>\t".$rec['name'].', '.$rec['surname'].': '.$rec['age']."\n";
}
}
function sort_field(&$data, $field, $reverse = false) {
usort($data, function ($a, $b) use ($field, $reverse) {
$collator = new Collator('pl_PL');
$compare = $collator->compare($a[$field], $b[$field]);
return $reverse ? -$compare : $compare;
});
}Podczas pierwszej refaktoryzacji kodu skorzystaliśmy z obiektowej funkcjonalności języka PHP, a konkretnie z klasy Collator. Dzięki niej możemy sortować tekst z uwzględnieniem lokalizacji, co zapewnia poprawne traktowanie polskich znaków diakrytycznych podczas sortowania. Dzięki temu nie musimy już definiować niestandardowych funkcji do zamiany polskich znaków diakrytycznych na ciągi ASCII.
W nowej wersji kodu, funkcja sort_field używa funkcji usort do posortowania tablicy $data na podstawie porównania elementów. Funkcja usort wymaga podania dwóch argumentów: posortowanej tablicy oraz funkcji porównującej, która określi kolejność elementów w tablicy.
Funkcja porównująca przyjmuje dwa argumenty, $a i $b, które są elementami tablicy $data. W przypadku tej funkcji, używamy słowa kluczowego use do przekazania wartości $field i $reverse do funkcji porównującej. W funkcji porównującej tworzymy obiekt $collator, który jest instancją klasy Collator z rozszerzenia intl. Tworzymy obiekt klasy Collator z użyciem polskiego locale ('pl_PL'), aby porównywać łańcuchy znaków zgodnie z polskimi zasadami sortowania.
Następnie wywołujemy metodę compare na obiekcie $collator, porównując wartości $a[$field] i $b[$field]. Metoda compare zwraca wartość ujemną, gdy pierwszy argument jest mniejszy niż drugi, wartość dodatnią, gdy pierwszy argument jest większy niż drugi, oraz zero, gdy oba argumenty są równe.
Jeśli wartość $reverse jest ustawiona na true, funkcja porównująca zwraca wartość przeciwną do wyniku porównania (czyli -$compare). W przeciwnym razie zwraca wynik porównania ($compare). Dzięki temu tablica $data zostaje posortowana według wartości klucza $field w rosnącej lub malejącej kolejności, w zależności od wartości $reverse.
Wykorzystywaliśmy funkcję display_data() do wyświetlania danych przed i po sortowaniu, tak jak w kodzie wyjściowym. Niestety, używaliśmy dwukrotnej pętli foreach. Wersja refaktoryzowana zamiast tego wykorzystuje tę samą funkcję do wyświetlenia danych przed i po sortowaniu, co prowadzi do krótszego i bardziej czytelnego kodu.
Dostosowanie kodu do PHP wersji 8
Po wprowadzeniu PHP w wersji 8 możemy kod funkcji skrócić do takiej postaci:
function sort_field(&$data, $field, $reverse = false): void {
$collator = new Collator('pl_PL');
usort($data, fn($a, $b) => $reverse ? -$collator->compare($a[$field], $b[$field]) : $collator->compare($a[$field], $b[$field]));
}Wprowadzone zmiany:
- Zamiast tworzyć obiekt
Collatorwewnątrz funkcji porównującej, tworzymy go przed wywołaniem funkcjiusort. Dzięki temu tworzymy tylko jeden obiektCollator, a nie jeden dla każdego porównania. - Zamieniliśmy anonimową funkcję na arrow function (fn($a, $b) => …), która pozwala na krótszy i bardziej czytelny zapis.
- Dodaliśmy typ zwracany
: void, który wskazuje, że funkcjasort_fieldnie zwraca żadnej wartości. Jest to dobra praktyka, aby wskazywać oczekiwane typy zwracane przez funkcje, ponieważ zwiększa to czytelność i może pomóc w wykrywaniu błędów.
W wyniku tych zmian, funkcja sort_field jest teraz krótsza i bardziej wydajna, gdyż tworzy tylko jeden obiekt Collator zamiast wielu. Ponadto, dzięki użyciu arrow function, kod jest bardziej zwięzły i czytelny.
Podsumowanie
W artykule omówiliśmy sortowanie tablic asocjacyjnych w PHP. Skupiliśmy się na optymalizacji kodu dla starszych wersji PHP i PHP 8, wykorzystując przykłady kodu. Pokazaliśmy, jak zoptymalizować obsługę polskich znaków oraz zmniejszyć liczbę obiektów klasy Collator. Przedstawiliśmy dwie wersje optymalizacji, jedną dla starszych wersji PHP i drugą dla PHP 8, wykorzystując arrow functions. Optymalizacje te są szczególnie istotne dla dużych zbiorów danych. Zrozumienie dostępnych funkcji i narzędzi w PHP oraz śledzenie nowości w kolejnych wersjach języka pozwoli Ci pisać bardziej wydajny i czytelny kod. Niezależnie od wersji PHP, istnieją techniki i podejścia, które możesz zastosować, aby ulepszyć swój kod. Kontynuuj naukę i eksperymentowanie, aby stać się jeszcze lepszym programistą PHP.