Sytuacja kobiet w IT w 2024 roku
26.02.20207 min
Jeremy Ruten

Jeremy RutenSoftware Developer7shifts: Restaurant Scheduling

Tablice w PHP to nie tablice

Poznaj koncepcję tablic w PHP i zobacz, jak prawidłowo do nich podchodzić.

Tablice w PHP to nie tablice

Tablica w PHP to bardzo wszechstronny twór - możesz jej używać jako mapy, typu zbiorowego, obiektu z danymi… lub jeśli jesteś odważny/a, to możesz jej też użyć jako tablicy! Okazuje się, że traktowanie tablicy w PHP w tradycyjny sposób (czyli jako listy wartości indeksowanych liczbami całkowitymi począwszy od 0) to droga pełna niebezpieczeństw. 

Dzieje się tak, ponieważ tablice w PHP nie są tak naprawdę tablicami. 

Zastanówmy się nad tym. Oto mała zagadka: co wydrukuje poniższy program?

<?php
$letters = ['d', 'c', 'a', 'b', 'e'];

$sorted = $letters;
natsort($sorted);

print $sorted[0];


Odpowiedź: drukuje d!

Kod ten próbuje wydrukować ciąg, który jest pierwszy w alfabecie, więc będzie to a. Jednak $sorted[0] niekoniecznie może oznaczać “pierwszą rzecz w tablicy”. Oznacza to bardziej “znajdź klucz 0 i zwróć wartość pod tym kluczem”. 

W PHP tablice to bardziej mapy (również znane jako tablice asocjacyjne). Mają one klucze i wartości. Kiedy tworzysz tablicę, jak poniżej: 

$ary = ['a', 'b', 'c'];

Co naprawdę się dzieje to: 

$ary = [0 => 'a', 1 => 'b', 2 => 'c'];


PHP przypisuje klucze będące liczbami całkowitymi sekwencyjnie od 0, więc tablica ta zachowuje się jak normalna, tradycyjna tablica. 

Spróbujmy zamieszać w kolejności tych kluczy:

<?php
$ary = [1 => 'b', 2 => 'c', 0 => 'a'];

print $ary[0]; // 'a'
print $ary[1]; // 'b'
print $ary[2]; // 'c'

// Prints: b c a
foreach ($ary as $letter) {
    print $letter;
}

// Prints: 1:b 2:c 0:a
foreach ($ary as $key => $letter) {
    print "$key:$letter";
}


Jeśli uzyskasz dostęp do tych tablic przez ich klucze, to nadal będą one zachowywać się jak tradycyjne tablice. Kiedy jednak wykonasz iterację przez tablicę, to klucze przestaną ją obchodzić. Zamiast tego podąży za kolejnością, w której dane elementy zostały zdefiniowane, czyli: b, c, a.

Wszystkie tablice w PHP mają klucze, wartości i specyficzną kolejność, w której te pary złożone z kluczy i wartość się znajdują. Innymi słowy:

tablica w PHP to właściwie uporządkowana mapa

Wracając do pierwszego przykładu: natsort() zmienia kolejność, w której znajdują się pary złożone z kluczy i wartości, ale bez zmieniania jakiegokolwiek z tych kluczy:

<?php
$letters = ['d', 'c', 'a', 'b', 'e'];

$sorted = $letters;
natsort($sorted);

print_r($letters); // [0 => 'd', 1 => 'c', 2 => 'a', 3 => 'b', 4 => 'e']
print_r($sorted);  // [2 => 'a', 3 => 'b', 1 => 'c', 0 => 'd', 4 => 'e']

print $sorted[0]; // 'd'


Zatem jeśli $sorted[0] nie pobierze pierwszej rzeczy w tablicy, jak ją właściwie uzyskać? Albo trzy pierwsze rzeczy? Istnieje kilka opcji:

array_values()

Możesz “przenumerować” tablicę z array_values(). Zwróci to tablicę z takimi samymi wartościami, które przekazaliście, ale z kluczami ponownie przypisanymi do liczb całkowitych, zaczynających się od 0.

<?php
$sorted = [2 => 'a', 3 => 'b', 1 => 'c', 0 => 'd', 4 => 'e'];

$sorted = array_values($sorted);
print $sorted[0]; // 'a'
print $sorted[1]; // 'b'
print $sorted[2]; // 'c'

array_slice()

Zazwyczaj gdy sięgamy do tablicy przez indeks, tak naprawdę mówimy o szukaniu klucza. array_slice() jest tutaj wyjątkiem - przekazywany indeks nawiązuje do pozycji w tablicy, a nie do klucza. 

<?php
$sorted = [2 => 'a', 3 => 'b', 1 => 'c', 0 => 'd', 4 => 'e'];

$first_three = array_slice($sorted, 0, 3);
print $first_three[0]; // 'a'
print $first_three[1]; // 'b'
print $first_three[2]; // 'c'


Mam jednak pewne zastrzeżenie: w przeciwieństwie do array_values(), array_slice() nadal będzie zachowywał klucze, które są stringami..

reset(), next() i ferajna

Każda tablica w PHP posiada wewnętrzny wskaźnik, który może zostać użyty do iteracji po tablicy. reset() ustawia wewnętrzny wskaźnik na początku tablicy. next() posuwa go do następnej pary klucz-wartość. Mamy również prev(), key(), current() oraz end(). Niskopoziomowe API jest brzydkie, ale daje dobre pojęcie o tym, jak naprawdę działają tablice w PHP.

<?php
$sorted = [2 => 'a', 3 => 'b', 1 => 'c', 0 => 'd', 4 => 'e'];

// start at the beginning
print reset($sorted); // 'a'
print key($sorted); // 2
print current($sorted); // 'a'

// advance forward
print next($sorted); // 'b'
print key($sorted); // 3
print current($sorted); // 'b'

// advance forward
print next($sorted); // 'c'
print key($sorted); // 1
print current($sorted); // 'c'

// jump to the end
print end($sorted); // 'e'
print key($sorted); // 4
print current($sorted); // 'e'

// there is no next element, so this returns null
print next($sorted);


Prawdopodobnie nigdy nie będziecie musieli używać tych funkcji, jednak czasem zobaczycie reset() jako wygodny sposób na złapanie pierwszej wartości, a end() na złapanie ostatniej wartości w tablicy. 

collect () Laravela

Laravel ma znakomitą klasę do obsługi kolekcji, która została przeistoczona w samodzielną bibliotekę, której używamy w 7shifts.  

Zapewnia ona metody first() i last(), aby pobrać pierwsze i ostatnie elementy tablicy oraz metodę take(), by zwrócić pierwszych N elementów.

<?php
$sorted = [2 => 'a', 3 => 'b', 1 => 'c', 0 => 'd', 4 => 'e'];

print collect($sorted)->first(); // 'a'
print collect($sorted)->last(); // 'e'
print_r(collect($sorted)->take(3)->all()); // [2 => 'a', 3 => 'b', 1 => 'c']


Zauważ, że take() zachowuje klucze tablicy. Wiele funkcji w tablicach PHP akceptuje opcjonalny argument o nazwie $preserve_keys. Biblioteka kolekcji prawie zawsze przekazuje true dla tego argumentu, kiedy funkcje tablic z PHP. Możesz się zatem zawsze spodziewać, że klucze będą zachowane podczas używania tej biblioteki. 

Kilka rad podczas pracy z tablicami PHP

Oto kilka rzeczy, których nauczyłem się podczas pracy z tablicami PHP. 

Ostrożnie podchodź do $ary[0]

Za każdym razem kiedy zobaczysz index 0 w tablicy, to kod prawdopodobnie zakłada, że ta tablica zachowuje się w tradycyjny sposób. Założenie takie nie jest jednak bezpieczne, szczególnie jeśli tablica została przekazana do Twojej funkcji z innego miejsca. 0 może zatem nie być pierwszym kluczem w tablicy, a co więcej, może nawet w ogóle nie istnieć (po, np. array_filter()).

Jeśli nie stworzyłeś tablicy samemu, powinieneś prawdopodobnie użyć reset($ary) albo takiej metody jak collect($ary)->first().

Rozważ przenumerowanie swoich tablic

Możesz zwalczyć problem związany z $ary[0] atakując też z innej strony: ilekroć przekazujesz tablicę do kolejnej funkcji, upewnij się, że ma ona klucze takie jak w tradycyjnej tablicy. Zwykle oznacza to, że wywołanie array_values() zaraz po array_filter() albo po jakiejkolwiek operacji może usunąć lub przestawić kolejność kluczy. 

Ciągła “naprawa” tablic z array_values() może wydawać się walką z ich prawdziwą naturą. Jednak, zamiast zmuszać je do zachowywania się jak prawdziwe tablice, może powinniśmy zaakceptować ich obecną formę i pisać kod, który nie będzie zakładał, z jakimi kluczami mamy do czynienia? Niemniej jednak, kwestia tablic w PHP może być niejasna dla nowicjuszy języka PHP, bo będą myśleć, że mają do czynienia z normalnymi tablicami.  

Powyższe dwa punkty czerpią z zasady niezawodności: “bądź konserwatywny w tym co wysyłasz i liberalny w tym, co akceptujesz od innych.”

To klucze mają znaczenie przy porównaniu tablic, a nie ich kolejność

Testy takie jak assertEquals(['a', 'b'], array_filter(['a', null, 'b'])) nie powiodą się, ponieważ [0 => 'a', 1 => 'b'] nie równa się [0 => 'a', 2 => 'b']! Klucze muszą do siebie pasować. 

Z drugiej strony, assertEquals($ary, collect($ary)->sort()->all()) zawsze się przeciśnie, ponieważ kolejność par klucze-wartości nie ma znaczenia dla ==. Na przykład, [0 => 'a', 1 => 'b'] == [1 => 'b', 0 => 'a'] jest prawdziwe. 

W obu przypadkach prawdopodobnie użyjesz array_values() do przenumerowania tablicy zanim zaczniesz je porównywać.

Używanie tablic jak map

Powiedzmy, że masz tablicę $users_by_id, gdzie $users_by_id[32] szukałoby użytkownika z id o wartości 32. Następnie, jeśli zmapujesz tę tablicę przy użyciu array_map(function ($user) { return $user->getName(); }), to otrzymasz tablicę nazw, której klucze to user id, ponieważ array_map() nie zmienia kluczy w tablicy. 

Zachowanie takiego klucza może być bardzo użyteczne, zatem jeśli pracujesz z mapami, możesz chcesz unikać funkcji, które ponownie numerują tablicę.

Używanie tablic jako posortowanych map 

Możesz posortować tablicę $users_by_id i następnie użyć jej zarówno do wyszukania użytkowników po ich id i przeiterowania po użytkownikach w konkretnej kolejności. Sztuczka ta uwolni Cię od potrzeby posiadania oddzielnej tablicy z id tylko po to, aby pamiętać ich kolejność, co trzeba również robić w niektórych innych językach. 

Zakomunikuj, jakim typem jest Twoja tablica

Sporo zamieszania z tablicami w PHP wynika z faktu, że jest to jedna struktura danych, która służy dwóm rzeczom. System typowania w PHP może nie rozróżniać między tradycyjnymi tablicami, a mapami, ale my powinniśmy. Przy braku odpowiedniego systemu typowania można użyć konwencji nazywania zmiennych, aby zakomunikować pewne typy. Na przykład, nazwy tradycyjnych tablic są często rzeczownikami w liczbie mnogiej, na przykład, $users, a nazwy map często kończą się “by x”, na przykład $users_by_id.

Podsumowanie

Sposób myślenia jest tutaj istotny. Kiedyś myślałem o tablicach w PHP jako o czymś wypełnionym parami klucz-wartość. Jednak, kiedy się zorientowałem, że jest to lista tych par ustawiona w konkretnej kolejności, zacząłem je naprawdę rozumieć. Musimy się jeszcze sporo nauczyć o tablicach PHP i dziwactwach wszystkich ich funkcji. Zrozumiałem, że jak się odpowiednio do nich podejdzie, to zaczynają mieć więcej sensu.



Oryginał tekstu w języku angielskim przeczytasz tutaj.

<p>Loading...</p>