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$data
jest 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$reverse
ma 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
$data
przed sortowaniem. - Sortowanie danych w tablicy
$data
za 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
$data
po 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
Collator
wewną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_field
nie 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.