Zicht goes open source: Zicht/Itertools

Boudewijn
Boudewijn - 30 december 2016

In software-ontwikkeling zijn er veel zaken die om herhaling vragen. In deze gevallen gebruiken we vaak een tool die dergelijke herhaling eenvoudig en efficient mogelijk maakt. Onze Itertools-bibliotheek is precies zo'n gereedschap voor het verwerken van opeenvolgende data in PHP.

(you can read the original English version on Medium)

Hoera, itertools open source! Het eerste waar je wellicht aan denkt is "Waarom een bibliotheek voor het afhandelen van opeenvolgende data?". En dat is een goede vraag. Ik heb een achtergrond in programmeren met Python en ben daarom gewend aan de consistente wijze waarop Python in staat is om te gaan met verschillende typen data (lists, dictionaries, strings, etc). In PHP ontbreekt dit grotendeels.

De functies count en sizeof bijvoorbeeld geven de lengte van een array, strlen de lengte van een string, en iterator_count de lengte van een Traversable. Python heeft de functie len, die precies dat doet voor alle bovenstaande situaties. Wanneer je voor alle verschillende datatypen één API hebt, maakt dat zaken een stuk eenvoudiger, en daarom minder code en dus minder bugs. Met andere woorden: blije developers.

Dus, de itertools library levert een consistente, door Python geinspireerde API voor het afhandelen van verschillende typen data in PHP.

Nog een itertools library?

Er is toch ongetwijfeld wel iemand die dit idee ook al heeft gehad en dit heeft gemaakt? Nou, als we op github kijken, zijn er 8 andere PHP repositories die melding maken van itertools. Maar, zoals we van veel open source gewend zijn, zijn deze ofwel incompleet, ongedocumenteerd, ongetest, niet onderhouden of een combinatie daarvan. Dus, een nieuwe bibliotheek is geboren.

Vanzelfsprekend is dan de uitdaging om te zorgen dat deze bibliotheek niet dezelfde valkuilen trapt:

1. Het moet compleet zijn (daar zijn we bijna)
2. Het moet gedocumenteerd zijn (dat is het)
3. Het moet getest zijn (unit tests code coverage staat op bijna 100%), en
4. Het moet actief onderhouden worden (check).

Wat je ermee kunt doen

Bij Zicht online maken we allerlei verschillende soorten websites, en vrijwel al die websites moeten op de een of andere manier data verwerken en in een gebruiksvriendelijke vorm tonen. Vaak is er dan sprake van groeperen van verschillende soorten data, waarbij bijvoorbeeld een stuk code als deze niet ongewoon is:

 $ticketTypes = [];
 
 foreach ($basket->items as $ticket) {
     if (!array_key_exists($ticket->type, $ticketTypes)) {
        $ticketTypes[$ticket->type] = [];
     }
     $ticketTypes[$ticket->type] []= $ticket;
 }

Dit blijven herhalen is onzinnig en daarom heeft itertools een eenvoudige `groupBy` voor deze toepassing:

 use function Zicht\Itertools\iterable;
 $ticketTypes = iterable($basket->items)->groupBy('type');

Laat ik wat van deze magic uitleggen. iterable($basket->items) geeft een object terug die de itertools API levert. In dit geval gebruiken we de groupBy('type') methode, die probeert een property of array key type te vinden en daar alle elementen uit de iterable op te groeperen. Het resultaat een verzameling van verzamelingen van tickets. In een array-notatie ziet dat er ongeveer zo uit:

 [
     'type A' => [ticket1, ticket2],
     'type B' => [ticket3, ticket4],
 ]

Uiteraard zijn er nog vele andere mogelijkheden, naast het groeperen, beschikbaar. Zaken als mappen, filteren, sorteren, reduceren, etc. Deze tools zijn uitgelegd met voorbeelden in onze github repository, dus ik zal ze hier niet herhalen. Bovenstaande is slechts bedoeld als smaakmaker!

Hoe je het toe kunt passen

Er zijn drie verschillende manieren waarop je de library kunt inzetten:

  1. 1. Object-oriented fluent interface
  2. 2. Functionele interface
  3. 3. In de Twig template language

Het voorgaande voorbeeld $iterable->groupBy('type') gebruikte de fluent interface. Dit is vooral erg handig als je meerdere bewerkingen achtereenvolgend wilt doen.

Bijvoorbeeld, je zou een lijst van tickets willen kunnen filteren voor een specifiek event, met vervolgens een groepering per type prijs, en van elk van deze prijzen de som willen berekenen. Dit kan met de volgende code:
$tickets->filter($isSpecifiedType)->map('price')->reduce()

Elke itertool is beschikbaar als een PHP trait, zodat het erg eenvoudig is om je eigen code aan te vullen met itertools-functionaliteit. Echter, we zouden hetzelfde ook toe kunnen passen met de functionele interface. In dat geval ziet het er als volgt uit: reduce(map('price', filter($isSpecifiedType, $tickets))).
Hoewel de resultaten hetzelfde zijn, is deze code minder leesbaar te noemen. Voor dit soort gevallen is het toepassen van de fluent interface vermoedelijk beter.

Tot slot levert de bibliotheek een Twig extension die vrijwel alle itertools ook als functies en filters aanbiedt. Dit is vooral handig om je te verzekeren van een goede scheiding tussen front- en backendcode. Om bij ons voorbeeld te blijven: de backend kan een lijst met tickets aan de twig template leveren, en de template kan deze groeperen bij events, en sorteren op datum. Op deze manier blijft de frontend verantwoordelijk voor hoe de data weergegeven wordt, zoals het hoort.

Maar wacht, er is meer

Als je dit allemaal gelezen hebt, dan hoop ik dat je itertools een kans wilt geven. Op dit moment wordt het in veel Zicht online projecten toegepast en heeft het zich al bewezen als hulpmiddel voor snellere en minder foutgevoelige ontwikkeling. En, misschien nog wel belangrijker, ik ben een blije developer.