Diversity w polskim IT
Chris Geelhoed
Chris GeelhoedWeb Developer @ Braid LLC

Upiększanie kodu w PHP za pomocą method chaining

Sprawdź, jak uatrakcyjnić Twój kod PHP za pomocą method chaining.
10.09.20214 min
Upiększanie kodu w PHP za pomocą method chaining

PHP jest uważany za brzydki język. Ewoluował on kawałek po kawałku i to widać. Ma jednak wiele zalet. Zespół programistów nieustannie go ulepsza i z każdą wersją, w szczególności PHP 7, czyni on wielkie postępy, zarówno pod względem funkcji, jak i wydajności.

Inne zalety tego języka, to przystępna dokumentacja, wszechobecny hosting i znaczna ilość zasobów online. I tak, wszystkie największe platformy CMS (Wordpress, Drupal, Joomla) są oparte na języku PHP.

Jak można zatem napisać piękny kod w PHP?

Krytykanci powiedzą, że to niemożliwe, ale frameworki takie jak Laravel („framework PHP dla rzemieślników internetowych”) już udowodniły, że się mylą. Jednym z wzorców, które Laravel i inne szanowane frameworki PHP często implementują, jest method-chaining, czyli łańcuchowe wywołania.

Oto przykład tego, o czym mówię: API do tworzenia zapytań Laravela:

$users = DB::table('users')
    ->where('votes', '>', 100)
    ->orWhere('name', 'John')
    ->get();


Ta „łańcuchowa” składnia jest niezła, ponieważ pozwala programistom pisać kod, który wykonuje komendę w ekspresyjny i czytelny sposób. W powyższym przykładzie można zobaczyć, co się dzieje:

  1. Spójrz na wiersze w tabeli users
  2. Gdzie wartość votes jest wyższa niż 100. 
  3. ...lub gdzie nazwa user to John.
  4. Pobierz wyniki.


Całkiem zgrabnie, prawda? Zobaczmy, jak wprowadzić ten schemat w życie.

Praktyka

Aby zilustrować method-chaining, spójrzmy na operacje tablicowe. PHP ma wiele pomocnych funkcji tablicowych, takich jak array_map, array_filter, usort itp., ale wykonywanie wielu operacji na jednej tablicy może zaboleć.

Załóżmy, że mamy tablicę nazw postaci z Simpsonów i chcemy zastosować następujące transformacje:

  1. Wyeliminuj wszystkie nazwy, które nie kończą się na „Simpson”.
  2. Zamień imię i nazwisko na imię, np. Lisa Simpson na Lisa.
  3. Sortuj nasze wyniki alfabetycznie.


Oto jeden ze sposobów rozwiązania naszego problemu:

$characters = [
    'Maggie Simpson',
    'Edna Krabappel',
    'Marge Simpson',
    'Lisa Simpson',
    'Moe Szyslak',
    'Waylon Smithers',
    'Homer Simpson',
    'Bart Simpson'
];
// Look for full names that end in Simpson
// Filter out items that don't match
$simpsons = array_filter($characters, function ($character) {
    return preg_match('/^.+\sSimpson$/', $character);
});
// Replace " Simpson" with an empty string for each item
$simpsons = array_map(function ($character) {
    return str_replace(' Simpson', '', $character);
}, $simpsons);
// Sort the items using PHP's "strcasecmp"
usort($simpsons, function ($a, $b) {
    return strcasecmp($a, $b);
});
var_dump($simpsons); // ['Bart', 'Homer', 'Lisa', 'Maggie', 'Marge']


To daje nam poprawny wynik, ale bystrzaki mogą zwrócić uwagę na kilka drobiazgów:

  1. array_filterakceptuje zarówno tablicę, jak i callback. array_map też, ale w odwrotnej kolejności. Dlaczego?
  2. Operacje filtrowania i mapowania są zapisywane w zmiennej $simpsons, ale usort uzyskuje dostęp do naszej tablicy przez referencję. Skąd ta niespójność?
  3. To nie wygląda zbyt ładnie. 


Zobaczmy dla porównania, jak można rozwiązać ten problem w JavaScript:

const simpsons = characters
  .filter(character => character.match(/^.+\sSimpson$/))
  .map(character => character.replace(' Simpson', ''))
  .sort((a, b) => b < a)
console.log(simpsons)


W JavaScript wygląda to o wiele bardziej elegancko. Tablice są rodzajem obiektu, co pozwala na połączenie operacji tablicowych. Ponadto, w powyższym przykładzie JS używa funkcji strzałkowych, żeby zachować zwięzłość. PHP jeszcze tego nie potrafi, ale już niedługo może umieć!

Method chaining w tablicach przychodzi łatwo, jeśli piszemy w JavaScript. Możemy to jednak naśladować w PHP, pisząc własną klasę.

Tworzenie klasy obsługującej method-chaining

Nazwijmy naszą klasę Collection. Instancję tej klasy można utworzyć, przekazując tablicę do konstruktora.

class Collection
{
    private $array;
    public function __construct($array)
    {
        $this->array = $array;
    }
}
$characters = new Collection([
    'Maggie Simpson',
    'Edna Krabappel',
    'Marge Simpson',
    'Lisa Simpson',
    'Moe Szyslak',
    'Waylon Smithers',
    'Homer Simpson',
    'Bart Simpson'
]);


Do tej pory nasza klasa niewiele robi - po prostu zapisuje tablicę przekazaną jako właściwość prywatną. Skoncentrujmy się na dodaniu publicznej metody filtrowania.

public function filter($callback)
{
    $this->array = array_filter($this->array, $callback);
    return $this;
}


Zamiast przekazywać zarówno tablicę, jak i callback (jak poprzednio), teraz możemy przekazać jedynie funkcję callback. Gdy wykonamy przekształcenie na własności instancji, zostanie zwrócona sama instancja. Dzięki temu możliwy jest method-chaining.

Dodajmy teraz metody dla map i sort:

public function map($callback)
{
    $this->array = array_map($callback, $this->array);
    return $this;
}
public function sort($callback)
{
    usort($this->array, $callback);
    return $this;
}


Teraz nasze metody są gotowe do wywołania łańcuchowego, ale nadal potrzebujemy pomysłu na uzyskanie końcowego rezultatu jako tablicy. Można to zrobić w sposób podobny do tego, w jaki Laravel używa get() do wykonania zapytania po zbudowaniu go z różnych warunków.

public function execute()
{
    return $this->array;
}


Podsumowując, nasza klasa wygląda następująco:

class Collection
{
    private $array;
    public function __construct($array)
    {
        $this->array = $array;
    }
    public function filter($callback)
    {
        $this->array = array_filter($this->array, $callback);
        return $this;
    }
    public function map($callback)
    {
        $this->array = array_map($callback, $this->array);
        return $this;
    }
    public function sort($callback)
    {
        usort($this->array, $callback);
        return $this;
    }
    public function execute()
    {
        return $this->array;
    }
}


Wreszcie możemy wywołać łańcuch metod na tablicy:

$characters = new Collection([
    'Maggie Simpson',
    'Edna Krabappel',
    'Marge Simpson',
    'Lisa Simpson',
    'Moe Szyslak',
    'Waylon Smithers',
    'Homer Simpson',
    'Bart Simpson'
]);
$simpsons = $characters
    ->filter(function ($character) {
        return preg_match('/^.+\sSimpson$/', $character);
    })
    ->map(function ($character) {
        return str_replace(' Simpson', '', $character);
    })
    ->sort(function ($a, $b) {
        return strcasecmp($a, $b);
    })
    ->execute();
var_dump($simpsons); // ['Bart', 'Homer', 'Lisa', 'Maggie', 'Marge']


Bardzo w stylu JavaScript! Problemy z kolejnością argumentów i dostępem do referencji nadal istnieją, ale nasza klasa Collection radzi sobie z tym za kulisami. Rezultatem jest kod, który jest znacznie czystszy i bardziej czytelny!

Podsumowanie

Język PHP ma złą reputację z powodu bałaganu, jaki w nim panuje, ale Laravel i inne frameworki używają już metod łańcuchowych (między innymi), które upiększają kod.

W przeciwieństwie do JavaScript, operacje tablicowe w PHP nie są łańcuchowe, ale można użyć podstawowej klasy pomocniczej (takiej, jak nasza klasa Collection!), aby to naśladować i utrzymać niektóre dziwactwa PHP z dala od naszych oczu.

<p>Loading...</p>