[PHP] Type-Hinting für elementare Datentypen

[PHP] Type-Hinting für elementare Datentypen

Ist an und für sich ein interessanter Ansatz für das Problem. =)
Rein prinzipiell auch schön geschrieben.

Aber ich schließe mich mermshaus and und bin der Meinung,
dass diese Lösung ziemlich inkonsistent ist.

Anlässlich dieses Themas habe ich mal auf GitHub wieder eine Klasse hochgeladen,
die ich für eine Art Pseudo-Type-Hinting verwende/verwendete.
-> https://github.com/c0mecl4rity/PHP-Developments/blob/develop/Type-Hinting/Types.php

PHP:
//Im Grunde läuft das Ganze noch nach diesem Prinzip ab:
if(!is_bool($var)) throw new \InvalidArgumentException...
if(!is_whatever($var))...

//Nur, dass die Schreibweise verkürzt wurde:
//In diesem Beispiel: boolean, null || integer, string, object || callable, [überspringen], resource
Types::are('b/-i/s/oc/-/r', func_get_args());

Das halte ich insofern für die bessere Lösung.
Auch wenn diese in einer gewissen Hinsicht auch unschön ist,
wenn man daran denkt, dass dieser Methodenaufruf in jede Funktion/Methode rein muss, in der man Überprüfen möchte.

Aber was will man machen? ^^ Hoffen, dass Hack sich durchsetzt und/oder,
dass es irgenwann mal optionale Type-Hints in PHP gibt.

Eine magische Methode like __toString wie "__toScalar", "__toWhatever" könnte das Problem schon lösen.
Dann könnte man entsprechende Klassen für seine Typen schreiben.

Und das ganze dürfte dann auch nicht mehr so den Augen
als auch der Programmeffizienz wehtun... :p
 
Zuletzt bearbeitet:
Ich habe über die Type-Hinting-Frage inzwischen alles in allem sicher schon tagelang nachgedacht und diskutiert und rumgeklickt. Da mir die Idee von einfach nur crack (und vor allem die doch zumindest teilweise gegebene syntaktische Eleganz) dennoch neu vorkam, sage ich an der Stelle noch mal prinzipiell Daumen hoch dazu, bevor es um andere Ansätze geht.

Bei recht exakt deinem Ansatz, c0mecl4rity, bin ich vor einiger Zeit auch angelangt:

- https://github.com/mermshaus/kaloa-util/tree/master/src/Kaloa/Util/TypeSafety
- https://github.com/mermshaus/kaloa-util/tree/master/tests/Kaloa/Tests/Util/TypeSafety (Tests/Demos)

Die interessante Datei ist diese: https://github.com/mermshaus/kaloa-util/blob/master/src/Kaloa/Util/TypeSafety/TypeSafetyTrait.php

Der Rest sind lediglich Wrapper, um den Trait auch über eine Klasse oder als Funktion anwenden zu können.

Ich habe in meinem Code bewusst darauf verzichtet, mehrere Datentypen für einen Parameter angeben oder zusammengesetzte Typen wie „numeric“ (für „float oder int“) verwenden zu können.

Der Verzicht auf mehrere/alternative Datentypen (im Sinne von „int oder string oder null“) ist sozusagen eine politische Entscheidung. Ich will tatsächlich strikten Umgang mit Datentypen forcieren und den Anwender dazu anleiten, gegebenenfalls alternative Lösungen (Wrapper-Objekte oder explizites Type-Casting oder dergleichen) zu nutzen. Wer das nicht will, kann immer noch den Parameter skippen (per "-") und eine entsprechende Prüfung von Hand durchführen. (Das fällt dann gewissermaßen unter erweitertes Validieren/Filtern, was nicht unbedingt eine Sache des Type-Hintings ist.)

Compound-Typen wie „numeric“ sind auch so ähnlich zu betrachten, weil es keinen Sinn ergibt, eine numerische Variable als „int“ oder „string“ vorliegen zu haben, wenn sie im Grunde ein „float“ ist. Weil „float“ der Zahlendatentyp mit dem größten Wertebereich ist, muss der nachfolgende Code sowieso von „float“ ausgehen.

Am Rande: Ich habe mir mal den erstbesten Online-Rechner genommen und mit 1e310 einen Wert eingetragen, der außerhalb des üblichen 64-Bit-Float-Bereichs liegt. So was berücksichtigt quasi niemand. (Überlegt mal, wie PHP-Code aussehen müsste, der nach jeder Berechnung prüft, ob ein Wert außerhalb des Float-Bereichs liegt…)

- http://ur1.ca/h3z4j (Man beachte das inf (infinite) und nan (not a number) in der Ausgabe.)

Das kann man aber als Indikator werten, dass Type-Hinting und Validierung vielleicht doch zusammengehören.​

Gerade bei den beiden Ausprägungen dieses Ansatzes sieht man aber wieder ganz gut ein Grundproblem (sowohl für Type-Hinting als auch für Hacks/Workarounds): Es ist bockschwer, das „richtig“ zu machen oder sich auf eine Umsetzung zu einigen, weil das Thema diverse Nuancen hat, die glaube ich kaum sinnvoll ausdiskutierbar sind, weil es schnell in Richtung allgemeiner Programmierphilosophie geht. Was PHP aktuell macht, ist ja auch nicht in dem Sinne „falsch“.

Es bleibt aber ein Thema, das man als Entwickler eigentlich nicht ignorieren kann, wenn man nicht will, dass seine Komponenten an random Punkten Fehler werfen.

PHP:
<?php

function f($s)
{
    // [...] Erster Teil eines wichtigen Codes

    $s . ' bar';

    // [...] Zweiter Teil eines wichtigen Codes, von dem wir gerne hätten, dass
    // er ausgeführt wird, weil die alleinige Ausführung des ersten Teils die
    // Software in einem unerwarteten Zustand zurücklässt
}

f(new stdClass()); // PHP Catchable fatal error

Man will Type-Checks, weil man diese bizarren Fehlerzustände vermeiden möchte, die unter Umständen sonst vorkommen könnten.

In diesem Fall wird man den Fehler vermutlich sogar noch bemerken. Wenn ich einen String in eine Funktion reinreiche, die irgendwas berechnet, wird der String im Zweifel auch zu int(0) gecastet. Das verhindert dann die Berechnung nicht, das Ergebnis wird bloß falsch. Das muss man dann erst mal mitbekommen. Vor derlei Fällen kann man sich mit Type-Checks immerhin ein wenig schützen, weil offensichtliche Falscheingaben dadurch abgefangen werden.



PS: func_get_args liefert keine Parameter mit default-Wert. get_defined_vars ist wahrscheinlich besser.

PHP:
<?php

function f($a, $b = 'foo')
{
    var_dump(func_get_args());
    var_dump(get_defined_vars());
}

f(12);

// array(1) {
//   [0]=>
//   int(12)
// }
// array(2) {
//   ["a"]=>
//   int(12)
//   ["b"]=>
//   string(3) "foo"
// }
 
Ich stimme dir da voll und ganz zu. ;)
Ich hab auch schon oft Diskussionen dazu gelesen, bisher liefen die alle irgendwo ins Leere.

Meine Variante ist ja weniger Type-Hinting als Validierungsgedöns.
(Im Grundegenommen sind meine Bezeichnungen insofern falsch, aber was solls. :p)

Ist halt nur eine Klasse,
die eine alternative bzw. kurze Schreibweise dieser umständlichen is-Funktionen bietet.
Ach so, und zu letzterem, wegen func_get_args:
PHP:
//Danke, dann lässt sich das auch verkürzen...
'-i' -> 'i'
Danke für den Hinweis. ;)
 
Zuletzt bearbeitet:
Auszug aus deinem Kommentar im Code:
mermshaus hat gesagt.:
[...] weil die alleinige Ausführung des ersten Teils die Software in einem unerwarteten Zustand zurücklässt
Dafür gibt es verschiedene Gegen-/Vorsichtsmaßnahmen:
  • Try-Catch: Im Catch-Teil wird alles wieder rückabgewickelt (-> Vermeidung des inkonsistenten Zustandes)
  • Asserts: Mehr oder Weniger dasselbe wie Validierungsmaßnahmen.
  • Transaktionen: Ähnlich wie bei Datenbanken, dass erst am Ende - bei vollständiger Durchführung der Datenverarbeitung und -validierung - die Daten eingetragen bzw. weitergegeben werden, sprich der Zustand geändert wird. Tritt ein Fehler auf, verharrt man beim alten Zustand.

Zu den zusammengesetzten Typen ("Compund-Typen")

Dafür gibt es einigen Sprachen Function Overloads (z. B. bei C++). Es nicht immer so, dass man die Typen ineinander umwandeln kann, man aber trotzdem eine Funktion für mehrere Typen haben will. Ein Grund ist da beispielsweise, dass die Funktion für alle Typen prinzipiell das Gleiche tut.
Wohlgemerkt muss man abwägen, ob man nicht doch mehrere Funktionen einführen will oder andere Lösungen vorzieht.

Validierung & Type Checks
Meiner Meinung nach sollte bei einem gut ausgearbeiteten System die eingängliche Validierung der Nutzereingaben (wohlgemerkt "Nutzer") nicht mit Type Checks oder "strikteren Validierungsmaßnahmen" innerhalb des "eigentlichen Codes" zusammenfallen.

Die eingängliche Validierung umfasst beispielsweise das Prüfen auf Ganzzahlen oder auf gültige Datumsangaben.
Type Checks: Es ist eigentlich klar, was das ist. Diese werden nur bei schwach typisierten Sprachen zum Problem.
Striktere Validierungsmaßnahmen (habe leider keinen besseren Namen auf die Schnelle gefunden): Sie tragen dazu bei, schlimme Fehler oder Inkosistenzen zu vermeiden. Allgemein helfen sie dem Programmierer, Fehler zu vermeiden. Dies geschieht beispielsweise durch Asserts.

Nun, was ich sagen möchte, ist, dass man als Programmierer einer Funktion/Bibliothek nicht immer jeden x-beliebigen Parameter prüfen sollte. Dies ist die Aufgabe des Callers. Durchläuft man alle Abstraktionsschichten kommt man irgendwann bei den Validierungsmaßnahmen von Nutzereingaben an. Und da sollten diese auch strikt sein!
Natürlich hängt es immer davon ab, inwieweit falsche Parameter kritische Zustände hervorrufen könnten.

Übrigens gibt es bei der Programmiersprache Ada für Integers auch einen Range-Type, welcher Zuweisungen von Zahlen außerhalb des definierten Bereiches mit Exceptions quittiert: http://en.wikibooks.org/wiki/Ada_Programming/Types/range
 
Es ist echt erstaunlich, was sich unter meinem Artikel für eine Diskussion entfacht hat. Um es mal auf den Punkt zu bringen: die Idee, die ich in meinem Skript umgesetzt habe, war nie für den Produktiveinsatz gedacht, sondern sollte nur zeigen, was mit PHP möglich ist. Praktisch gesehen bin ich auch ein Freund der ensure-Variante oder der PHP-eigenen Filter-Funktionen.

Generell ist das fehlende (konsistente) Typehinting in PHP mein größtes Problem in PHP neben der unsauberen und unlogischen Benennung und Umsetzung von Funktionen, Methoden und Klassen, und es für mich ein "pain in the ass", wenn ich jedes mal am Anfang einer Funktion in mehrerern Bedingungen prüfen muss, ob mir die korrekten Daten übergeben wurden – aber Ihr kennt das doch selber.
 
Oooh ja, deshalb wahrscheinlich auch die Diskussion. :)

ComFreek hat gesagt.:
[Zur Vermeidung inkonsistenter Zustände bei plötzlichen Fehlern] gibt es verschiedene Gegen-/Vorsichtsmaßnahmen

Ja, mir ging es bei dem Beispiel darum, zu zeigen, dass man nicht einfach sagen kann, dass es das implizite Type-Casting von PHP schon richten wird. Es gibt eben Fälle, in denen falsche Datentypen reingereicht werden können, die dann irgendwann später zu Fehlern führen. Das sollte zeigen, dass man sich als PHP-Entwickler mit Type-Hinting beziehungsweise mit korrekten Datentypen befassen muss, ob man will oder nicht. Die Alternative wäre höchstens, zu sagen, dass die Funktion schon niemand falsch aufrufen wird. So möchte ich aber ungern Code schreiben, zumal entsprechende Checks auf korrekte Datentypen ja relativ simpel möglich sind. Es ist bloß Schreibarbeit, wenn man wirklich immer von Hand if (!is_string($param)) throw new Exception(); tippt. Mit den entsprechenden Ansätzen hier geht es ja darum, diese Schreibarbeit zu reduzieren. Letztlich sollte das aber optimalerweise die PHP-Engine automatisch übernehmen. Das ist zumindest ein Wunsch, den viele Entwickler haben. Das ist historisch betrachtet nur nicht unbedingt die Philosophie von PHP.

Zu den zusammengesetzten Typen ("Compund-Typen")

Alternativen wären Overloading oder auch Konzepte, die manche Umwandlungen ohne explizites Anweisen zulassen (int nach float), die andere aber wegen potenziellem Datenverlust erst mal zurückweisen (float nach int). In Java etwa:

Code:
public class Foo
{
    public static void main(String[] args)
    {
        int    i = 10;
        double d = i;      // funktioniert

        String s = "" + d; // funktioniert halb, das  "" +  ist im Grunde auch
                           //   schon explizites Casting
        
        double e = 10.5;
        int    j = e;      // error: possible loss of precision
                           //   Das wäre explizit etwa per  int j = (int) e;
                           //   möglich
    }
}

Anders angefangen: Es braucht keinen Compound-Type „numeric“ (der jetzt der Einfachheit halber nur int und float umfasst), wenn bei einem float-Type-Hint ein int-Parameter automatisch nach float gecastet wird. So macht es wiederum Java:

Code:
public class Foo
{
    public static void main(String[] args)
    {
        Foo app = new Foo();
        int i   = 10;
        
        app.demo(i);
    }
    
    public void demo(double f)
    {
        System.out.println(f); // Gibt 10.0 aus (also Fließkomma), nicht 10 (int)
    }
}

Dass in dem PHP-Compound-Type „numeric“ noch andere Datentypen (zumindest string) drinstecken, ist zusätzlich wirr. Das macht etwa Java nicht mit. Da geht es zum Beispiel so:

Code:
String s = "10";
app.demo(Double.parseDouble(s));

Oder alternativ per Overloading, wenn man drauf steht:

Code:
public class Foo
{
    public static void main(String[] args)
    {
        Foo app  = new Foo();
        String s = "10";
        
        app.demo(s);
    }
    
    public void demo(double f)
    {
        System.out.println(f);
    }
    
    public void demo(String s)
    {
        demo(Double.parseDouble(s));
    }
}

Ich empfinde das als wesentlich sauberer und mag deshalb ein Konzept von einem „numeric“ als – zumindest – Pseudo-Datentyp nicht wirklich.

Dazu auch noch diese Seite, die einen ganz netten Einblick bietet, wie kompliziert so Datentypen und Umwandlungen sein können: http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html Wenn es einfach wäre, hätte PHP das vermutlich wirklich schon lange. Man muss sich eben richtig in die Thematik einarbeiten und dann Lösungen finden, die in PHP – immerhin eine Interpretersprache, Kompilierung muss also schnell gehen – möglich und sinnvoll sind und zum Ökosystem (interne Funktionen, Extensions) passen.



Nun, was ich sagen möchte, ist, dass man als Programmierer einer Funktion/Bibliothek nicht immer jeden x-beliebigen Parameter prüfen sollte. Dies ist die Aufgabe des Callers. Durchläuft man alle Abstraktionsschichten kommt man irgendwann bei den Validierungsmaßnahmen von Nutzereingaben an. Und da sollten diese auch strikt sein!

Ja… Schwieriges Thema. Ich programmiere sehr komponentenorientiert, also ich organisiere Code sozusagen in Einheiten, die über so eine Art API miteinander verbunden sind. Bei den Einsprungspunkten der Komponenten, bei dem Code, der von anderen Komponenten aufgerufen wird, bin ich gerne strikt, was Eingaben angeht. Innerhalb der Komponenten validiere ich dann eigentlich nicht mehr. Das ist so die Idee, dass ich innerhalb der Komponente selbst dafür sorgen kann, dass ich die passenden Sachen übergebe, weil dort außer mir niemand rumzupfuschen hat.

Diese Sichtweise kann man natürlich beliebig „skalieren“. Man kann durchaus auch sagen, dass in einer Anwendung alles außer den Nutzereingaben sauber ist. Ich finde das daher schwierig pauschal zu diskutieren. Das sind Themen, die sehr weitläufig sind.

Übrigens gibt es bei der Programmiersprache Ada für Integers auch einen Range-Type, welcher Zuweisungen von Zahlen außerhalb des definierten Bereiches mit Exceptions quittiert

Auch das ist ein interessanter Aspekt, weil das demonstriert, dass selbst ein Type-Hint, der nur den passenden Datentyp durchlässt, noch nicht das Ende des syntaktisch Möglichen ist. Das zeigt auch, wie sehr das, was in Programmiersprachen in Syntax gegossen wird und was nicht, willkürliche Konvention ist. Syntax ist letztlich eben auch bloß eine Metapher zur schriftlichen Abbildung von Modellen oder von Realität. Uff.



einfach nur crack hat gesagt.:
Praktisch gesehen bin ich auch ein Freund der ensure-Variante oder der PHP-eigenen Filter-Funktionen.

Letztere sind leider grottig dokumentiert, finde ich.

Generell ist das fehlende (konsistente) Typehinting in PHP mein größtes Problem in PHP […] und es für mich ein "pain in the ass" […].

Ja.
 
Zuletzt bearbeitet von einem Moderator:
Zurück