Ga direct naar de inhoud
DedicatedPHP Contact

Een PHP-API ontwerpen met stabiele paginering terwijl gegevens veranderen

Leer kiezen tussen offset en cursor, een deterministische sortering vastleggen en omgaan met invoegingen, verwijderingen en filters om duplicaten of ontbrekende resultaten te voorkomen.

Diagram van een PHP-API die resultaatpagina's vervolgt met een cursor en een deterministische sortering

Een gepagineerd antwoord kan correct zijn op het moment dat het wordt opgevraagd en toch een inconsistente doorloop opleveren. Als een applicatie een pagina opvraagt, de dataset verandert en vervolgens de volgende pagina opvraagt, kan ze dubbele items ontvangen of andere items missen. Dit komt vaak voor bij activiteitenoverzichten, bestellingen en logs die blijven groeien.

Paginering met cursors in een PHP-API helpt om deze verschuiving te beheersen, maar garandeert op zichzelf geen bevroren weergave van de gegevens. De belangrijkste beslissing is vast te leggen wat het betekent om door de lijst te navigeren, welke wijzigingen tijdens die doorloop kunnen plaatsvinden en welk contract de client nodig heeft.

Waarom resultaten tussen pagina's veranderen

Waarom resultaten tussen pagina's veranderen — guía visual de DedicatedPHP

Stel dat een query de records op aflopende datum sorteert. De client haalt de eerste 20 op. Voordat de client de volgende pagina opvraagt, worden er drie recente records ingevoegd. Als het tweede verzoek OFFSET 20 gebruikt, begint het op positie 21 van de huidige dataset, niet op positie 21 zoals die was bij het eerste verzoek. Sommige items van de eerste pagina kunnen opnieuw verschijnen.

Er kunnen ook items ontbreken. Als een record vóór de offset wordt verwijderd, schuiven de volgende items één positie op en kan een rij die de client verwachtte achterblijven. De volgorde is evenmin noodzakelijk stabiel als meerdere rijen dezelfde datum hebben: zonder een aanvullend criterium hoeft de database deze gelijke waarden niet altijd in dezelfde volgorde terug te geven.

Maak onderscheid tussen twee doelen: verschuivingen als gevolg van positiewijzigingen voorkomen en een exacte momentopname van de volledige dataset aanbieden. Goed gedefinieerde paginering met cursors helpt bij het eerste doel. Het tweede vereist een expliciete consistentiestrategie, die meer kosten met zich mee kan brengen en afhankelijk kan zijn van de database.

Offset of cursor: kies op basis van het leespatroon

Paginering op basis van offset, doorgaans uitgedrukt met LIMIT en OFFSET, is eenvoudig en maakt het mogelijk rechtstreeks naar een bekende pagina te gaan. Dit kan geschikt zijn voor kleine of relatief statische datasets, interfaces waarin vaak tussen pagina's wordt gesprongen en situaties waarin inconsistenties tijdens het navigeren acceptabel zijn. Bij grote datasets kunnen hoge offsets de database dwingen veel rijen te doorlopen of over te slaan; de werkelijke kosten hangen af van de database-engine, de indexen en de query.

Paginering met cursors retourneert een verwijzing naar het punt waarvandaan de client verder kan gaan, bijvoorbeeld de laatste sorteerwaarde en de bijbehorende unieke sleutel. De volgende query zoekt records na of vóór dat punt, in plaats van een aantal rijen over te slaan. Dit past bij sequentiële doorlopen, feeds en overzichten waarin regelmatig nieuwe records worden ingevoegd. Daar staat tegenover dat een cursor niet vanzelf naar een willekeurige pagina springt: de client moet pagina's doorlopen of een andere strategie gebruiken.

De keuze hoeft niet voor de hele API hetzelfde te zijn. Je kunt offset aanbieden in een beheerdersquery met genummerde pagina's en een cursor in een activiteitenstroom. De interface moet weergeven wat de server kan garanderen, in plaats van willekeurige navigatie en absolute stabiliteit met één mechanisme te beloven.

Leg een totale volgorde vast voordat je de cursor maakt

De cursor identificeert alleen een positie als de sortering deterministisch is. Alleen sorteren op created_at volstaat niet wanneer twee records dezelfde datum hebben. Voeg een unieke, onveranderlijke kolom toe als doorslaggevend criterium, bijvoorbeeld id:

ORDER BY created_at DESC, id DESC

Zo heeft elke rij een vastgelegde positie in de volgorde. De cursor om verder te gaan moet beide waarden bevatten. Bij dezelfde aflopende sortering zoekt de volgende query naar paren die kleiner zijn dan het laatst teruggegeven paar:

WHERE created_at < :cursor_date
   OR (created_at = :cursor_date AND id < :cursor_id)
ORDER BY created_at DESC, id DESC
LIMIT :page_size

Bij een oplopende sortering worden de vergelijkingen omgedraaid. Bij meerdere criteria moeten de voorwaarden de volledige lexicografische volgorde volgen: eerst wordt het eerste veld vergeleken en bij gelijke waarden het volgende. Als de richtingen verschillen — bijvoorbeeld aflopende datum en oplopend identificatienummer — moet elke vergelijking overeenkomen met de richting van de betreffende kolom. Het volstaat niet om alle operatoren tegelijk om te draaien.

Ook de sorteerwaarden hebben stabiele regels nodig. Als een kolom null kan zijn, leg dan vast hoe die waarden worden gesorteerd en codeer dat onderscheid in de voorwaarde om verder te gaan. Gebruik bij voorkeur criteria die tijdens de doorloop onveranderlijk blijven: als een datum die de positie bepaalt wordt gewijzigd, kan een rij van de ene naar de andere kant van de cursor verschuiven.

Maak de cursor opaak, gevalideerd en gekoppeld aan de query

Een cursor kan de sorteerwaarden serialiseren en coderen, bijvoorbeeld met Base64URL. Opaak betekent dat de consument de cursor niet hoeft te interpreteren of zelf samen te stellen, niet dat Base64 de cursor beschermt. Als wijziging van de waarden de reikwijdte van de query kan veranderen, valideer dan het formaat en onderteken de inhoud met een HMAC of gebruik een gelijkwaardig integriteitsmechanisme. Neem geen geheimen of onnodige persoonsgegevens op.

Valideer typen, verwachte velden, de versie van het formaat en de maximale grootte voordat je de database raadpleegt. Gebruik SQL-parameters voor de waarden. Kolomnamen en sorteerrichtingen mogen niet rechtstreeks uit de cursor of het verzoek worden overgenomen: ze moeten afkomstig zijn uit een allowlist op de server.

Een cursor met een datum en identificatienummer mag niet per ongeluk opnieuw worden gebruikt met andere filters als dat tot een misleidend vervolg leidt. Je kunt een canonieke weergave opnemen van de relevante filters, de sorteerrichting en, indien van toepassing, de paginagrootte, en deze samen met het vervolgpunt ondertekenen. Als deze niet overeenkomen met het huidige verzoek, retourneer dan een duidelijke fout in plaats van stilzwijgend met een andere query verder te gaan. Centraliseer in PHP de codering, validatie en ondertekening om te voorkomen dat regels tussen controllers worden gedupliceerd.

Bepaal welke consistentie je bij gelijktijdige wijzigingen biedt

Bij een doorloop over actuele gegevens raadpleegt elke pagina de status die op dat moment beschikbaar is. Een cursor op basis van een onveranderlijke sortering voorkomt veel verschuivingen door invoegingen vóór het bereikte punt. Maar hiermee ontstaat geen momentopname: er kunnen nieuwe rijen na de cursor verschijnen, nog niet bezochte rijen kunnen worden verwijderd en toepasselijke rechten en filters kunnen veranderen. Documenteer dit gedrag, zodat de client het niet verwart met een afgesloten export.

Als het product vereist dat alle pagina's een afgebakende dataset vertegenwoordigen, kun je een grens vastleggen, zoals een datum of een maximaal identificatienummer bij het begin van de doorloop, en die aan elke query toevoegen. Hiermee worden invoegingen na de grens uitgesloten wanneer het gekozen criterium dat mogelijk maakt, maar verwijderde rijen blijven niet behouden en een perfecte momentopname bij wijzigingen wordt niet gegarandeerd. Een andere mogelijkheid is een transactionele momentopname; een transactie openhouden tussen verzoeken heeft doorgaans operationele en resourcegevolgen en moet daarom niet zonder meer als standaardoplossing worden beschouwd.

Het contract kan duidelijke grenzen aangeven: ondersteunde sortering, filters die ongewijzigd moeten blijven, eventuele vervaltijd, gedrag bij een ongeldige cursor en of gelijktijdige wijzigingen de dataset kunnen veranderen. Beloof niet dat duplicaten of ontbrekende resultaten absoluut worden voorkomen als de strategie dat niet kan garanderen.

Test de grensgevallen en documenteer het contract

Test de grensgevallen en documenteer het contract — guía visual de DedicatedPHP

Tests moeten de volledige doorloop controleren, niet alleen de vorm van een antwoord. Maak rijen met herhaalde sorteerwaarden en controleer dat meerdere samengevoegde pagina's de verwachte volgorde opleveren zonder duplicaten. Neem gevallen op waarin de paginagrootte een groep gelijke waarden opsplitst en valideer zowel de oplopende als de aflopende richting.

  • Voeg tussen twee verzoeken records vóór en na de cursor in en controleer of het afgesproken gedrag wordt gevolgd.
  • Verwijder een nog niet opgehaalde rij en wijzig een sorteerkolom, als het model dat toestaat; documenteer de gevolgen.
  • Wijzig een filter, de sortering of de doorlooprichting en controleer of een niet-compatibele cursor wordt geweigerd.
  • Verstuur onjuist opgemaakte, gewijzigde of te grote cursors, of cursors met waarden van onjuiste typen.
  • Controleer de grenzen voor de paginagrootte en het geval zonder resultaten, inclusief het ontbreken van een volgende pagina.

Registreer voor diagnostiek meetgegevens over de queryduur, paginagrootte en validatiefouten, zonder gevoelige cursors of persoonsgegevens vast te leggen. Als items terugkeren, controleer dan eerst de totale volgorde en de voorwaarde om verder te gaan. Als het probleem de querykosten betreft, inspecteer dan het uitvoeringsplan en de indexen op de sorteer- en filtervelden.

Stabiele paginering hangt niet af van het verbergen van een tekenreeks in Base64: ze hangt af van een deterministische volgorde, een consistente vergelijking, gecontroleerde filters en expliciete verwachtingen over gelijktijdige wijzigingen. Met die keuzes worden offset en cursor opties die je kunt selecteren op basis van de daadwerkelijke doorloop die de client nodig heeft.

Wil je deze ideeën toepassen op je project?Laten we uw PHP-platform bespreken.
Bekijk gerelateerde service