Hi.
Ich versuche derzeit für eine Library zu evaluieren, wie mit Beziehungen zu externen Ressourcen (im Sinne von Dateien) umgegangen werden kann.
Sagen wir mal, ich will eine Methode wie DOMDocument::load schreiben, die eine Datei einliest und in irgendeiner Form verarbeitet.
Einige denkbare Signaturen:
Variante 1: „Ich will mit dem Dateisystemzugriff nichts zu tun haben, gib mir einfach nur die Daten.“ Das ist ein schönes Level an Abstraktion, aber alle Daten müssen zeitgleich und vielleicht gar mehrfach im Speicher gehalten werden, was sehr ineffizient sein kann und in manchen Fällen überhaupt keine Option darstellt.
Variante 2: „Gib mir einen URL und ich versuche selbst mein Glück und sage Bescheid, wenn es fehlschlägt.“ Hier habe ich bewusst $url geschrieben und nicht etwa $localPath, um anzudeuten, dass $url im Grunde alle fopen-Wrapper umfasst und auch alle per stream_wrapper_register zusätzlich definierten Wrapper.[1] Da stehen die Chancen relativ gut, dass die Methode den URL überhaupt nicht „geöffnet“ bekommt, weil die Ressource etwa gar nicht gelesen werden kann oder sonst irgendwas ist. Diese Fehler müssen dann irgendwie formuliert und an den aufrufenden Code weitergereicht werden. Zudem muss eine Entscheidung getroffen werden, ob und für welche Protokolle ein Lock gesetzt wird und was passiert, wenn das fehlschlägt, oder auch, auf welche Weise genau auf den Stream zugegriffen wird (fopen-Modi). Anders gesagt: Das ist reichlich unübersichtlich und genau das Gegenteil von Abstraktion.
Variante 3: „Gib mir einen Stream, der so eingestellt ist, wie du es willst. Ich funke da nicht zwischen, sondern sag dir höchstens an, falls es knallt.“ Im Vergleich zu Variante 2 verlangt diese Variante, dass der aufrufende Code alle notwendigen Entscheidungen trifft und bereits einen Stream bereitstellt, der dann ja ganz offensichtlich geöffnet werden konnte. Zudem ist theoretisch eine sehr flexible Einbindung möglich, denn auch Funktionen wie popen erzeugen gleichartige Streams. Auch lässt sich ein solcher Stream sicherlich hübsch als Klasse zusammenfassen, was garantiert auch bereits jemand getan hat.
Variante 4: void load(stream $in, stream $out). Das ist für normale Anwendungsfälle wohl zu viel des Guten, auch wenn sich ein Out-Stream etwa per stream_get_contents wieder in einen String verwandeln ließe.
Alle Varianten außer 2b decken den Umfang der jeweiligen Vorgängervariante vollständig ab.
Ich tendiere zu Variante 3. Was meint ihr?
PS: Mir ist schon klar, dass das alles nicht wirklich neu ist, aber es ist ja mehr oder weniger eine Tatsache, dass Variante 2 vorherrschend ist. Warum eigentlich? Weil wir nicht Java sind?
1: Auch möglich: $url = 'data://text/plain;base64,' . base64_encode($data);
Ich versuche derzeit für eine Library zu evaluieren, wie mit Beziehungen zu externen Ressourcen (im Sinne von Dateien) umgegangen werden kann.
Sagen wir mal, ich will eine Methode wie DOMDocument::load schreiben, die eine Datei einliest und in irgendeiner Form verarbeitet.
Einige denkbare Signaturen:
- load(string $data)
- load(string $url)
- load(stream $stream)
Variante 1: „Ich will mit dem Dateisystemzugriff nichts zu tun haben, gib mir einfach nur die Daten.“ Das ist ein schönes Level an Abstraktion, aber alle Daten müssen zeitgleich und vielleicht gar mehrfach im Speicher gehalten werden, was sehr ineffizient sein kann und in manchen Fällen überhaupt keine Option darstellt.
Variante 2: „Gib mir einen URL und ich versuche selbst mein Glück und sage Bescheid, wenn es fehlschlägt.“ Hier habe ich bewusst $url geschrieben und nicht etwa $localPath, um anzudeuten, dass $url im Grunde alle fopen-Wrapper umfasst und auch alle per stream_wrapper_register zusätzlich definierten Wrapper.[1] Da stehen die Chancen relativ gut, dass die Methode den URL überhaupt nicht „geöffnet“ bekommt, weil die Ressource etwa gar nicht gelesen werden kann oder sonst irgendwas ist. Diese Fehler müssen dann irgendwie formuliert und an den aufrufenden Code weitergereicht werden. Zudem muss eine Entscheidung getroffen werden, ob und für welche Protokolle ein Lock gesetzt wird und was passiert, wenn das fehlschlägt, oder auch, auf welche Weise genau auf den Stream zugegriffen wird (fopen-Modi). Anders gesagt: Das ist reichlich unübersichtlich und genau das Gegenteil von Abstraktion.
Variante 2b: Es werden nur solche Pfade zugelassen, die auf das lokale Dateisystem zeigen. Wer was anderes übergibt, hat selbst Schuld. Das ist vermutlich das, was die meisten Methoden, die Pfade als Parameter nutzen, implizieren. Durchsetzen werden es die wenigsten. Diese Variante hat allerdings so oder so noch immer mit einem Teil der Probleme von Variante 2 zu tun.
Variante 3: „Gib mir einen Stream, der so eingestellt ist, wie du es willst. Ich funke da nicht zwischen, sondern sag dir höchstens an, falls es knallt.“ Im Vergleich zu Variante 2 verlangt diese Variante, dass der aufrufende Code alle notwendigen Entscheidungen trifft und bereits einen Stream bereitstellt, der dann ja ganz offensichtlich geöffnet werden konnte. Zudem ist theoretisch eine sehr flexible Einbindung möglich, denn auch Funktionen wie popen erzeugen gleichartige Streams. Auch lässt sich ein solcher Stream sicherlich hübsch als Klasse zusammenfassen, was garantiert auch bereits jemand getan hat.
Variante 4: void load(stream $in, stream $out). Das ist für normale Anwendungsfälle wohl zu viel des Guten, auch wenn sich ein Out-Stream etwa per stream_get_contents wieder in einen String verwandeln ließe.
Alle Varianten außer 2b decken den Umfang der jeweiligen Vorgängervariante vollständig ab.
Ich tendiere zu Variante 3. Was meint ihr?
PS: Mir ist schon klar, dass das alles nicht wirklich neu ist, aber es ist ja mehr oder weniger eine Tatsache, dass Variante 2 vorherrschend ist. Warum eigentlich? Weil wir nicht Java sind?
1: Auch möglich: $url = 'data://text/plain;base64,' . base64_encode($data);

Stichwort Race-Conditions eben. Die müssen zwar in den meisten Fällen sicherlich nicht vermieden werden, weil kein Lese-Schreibzugriff vorgesehen ist. Aber wenn es möglich ist, sie zu vermeiden, warum denn nicht?
Kommentar