Facebook
Twitter
Google+
Kommentare
27

Undefinierte Attribute

Heute möchte ich ein wenig übe rein Phänomen reden, dass ich in letzter Zeit immer häufiger sehe. Undefinierte Attribute. Ich nenne es mal so, keine Ahnung, ob es dazu einen Fachausdruck gibt. Auf jeden Fall meine ich Instanzvariablen, die nicht definiert wurden, aber mit denen hantiert wird.

Natürlich habe ich auch gleich ein kleines Beispiel:

class Foo
{
  function setVar( $value )
  {
    $this->var = $value;
  }
}

Rein technisch gesehen, würde ich sagen, dass dies identisch mit der folgenden Klasse ist.

class Foo
{
  public $var = null;

  function setVar( $value )
  {
    $this->var = $value;
  }
}

Das erste Beispiel ist kürzer und hat trotzdem die gleiche Funktionalität. PHP macht es möglich. Leider.Wenn man die beiden vergleicht, dann würde man ganz naiv sagen, dass die erste Variante vorzuziehen ist, da sie weniger komplex scheint. Was macht man aber, wenn mit statischer Codeanalyse arbeiten will? Ich habe ja gar keine Ahnung, ob die Variable $this->var existiert oder ob das ein Fehler in der Programm ist. Vertipper könnte ich so zum Beispiel nicht finden (schreibe übrigens gerade an einem Code Sniffer Sniff dafür). Auch die IDE kann mir nicht helfen. Dank PHPDoc könnte ich nämlich im zweiten Fall meiner Entwicklungsumgebung sagen, was für ein Typ die Variable $var ist. Und da ich sehr viel mit meiner IDE mache, wäre das für mich schon das K.O. Kriterium.

Dabei habe ich aber immer ein Problem. PHP erlaubt viele Dinge, die ich für relativ unsauber halte. __set und __get gehören auch zu dieser Spezies. Diese beiden magischen Methoden gehören aber fest zu PHP hinzu. Ich würde sie nur im Notfall verwenden. Trotzdem würde ich sie niemals verbieten.Genau so ist es auch bei diesem Beispiel. PHP ist so konstruiert worden, dass man so etwas machen kann. Es ist also jedem selbst überlassen, ob er seine Attribute vorher definiert.

Dummerweise ist auch gerade 1 Uhr nachts und ich habe keine Lust mehr ein Beispiel aus irgendeinem Produktivcode zu suchen, deswegen müsst ihr euch jetzt mal mit meinem hinkenden zufrieden geben.

Über den Autor

Nils Langner

Nils Langner ist der Gründer von "the web hates me" und auch der Hauptautor. Im wahren Leben leitet er das Qualitätsmanagementteam im Gruner+Jahr-Digitalbereich und ist somit für Seiten wie stern.de, eltern.de und gala.de aus Qualitätssicht verantwortlich. Nils schreibt seit den Anfängen von phphatesme, welches er ebenfalls gegründet hat, nicht nur für diverse Blogs, sondern auch für Fachmagazine, wie das PHP Magazin, die t3n, die c't oder die iX. Nebenbei ist er noch ein gern gesehener Sprecher auf Konferenzen. Herr Langner schreibt die Texte über sich gerne in der dritten Form.
Kommentare

27 Comments

  1. Also in unserem (eigenen) Framework werden undefinierte/unbekannte Membervariablen komplett abgefangen und entsprechend ins errorLog geschrieben. Dazu leiten alle Klassen im System von einer Basisklasse ab die mit einer magischen __get Methode ausgestattet ist:

    class BaseClass
    {
    public function __get($var)
    {
    self::errorLog(‚Undefined member variable: ‚ . $var);
    }
    }

    eigentlich ganz easy, und macht einem vor Allem das Leben deutlich einfacher 🙂

    Reply
  2. Diese Variablen/Attribute sind „implizit deklariert“. Das mit dem Typ im PHPDoc taugt wohl kaum zur Analyse des Codes, da du ja jederzeit (absichtlich oder versehentlich) einen ganz anderen Typ zuweisen kannst. Was die IDE betrifft, kannst du doch zum Beispiel in Eclipse PDT auch ohne explizite Deklaration Type Hints hinzufügen: .

    Reply
  3. Was mich am meisten bei dieser Variante von PHP stört, wenn man z.B. folgendes hat:

    class Foo
    {
    public $bar;
    }

    $fooBar = new Foo();
    $baar = 1;

    Habe ich nun 2 Membervariablen: $bar und $baar – PHP meldet den Fehler nicht. Sowas kann einem manchmal zur Weißglut treiben 😉 Es wäre schön, wenn man das per Config-Parameter verbieten könnte!

    Reply
  4. rbq: Das letzte Mal, als ich PDT benutzt hatte, konnte es aber noch nicht mit Typehints für nicht explizit deklarierte Klasseneigenschaften umgehen – geht das inzwischen? Das @var ging ja sogar schon im „alten“ Prä-Eclipse Zend Studio.

    Nils: Amen. Das ist einer der vielen Gründe, warum ich z.B. Zend_DB nicht mag. Insgesamt ist meine Erfahrung bisher: Je mehr magische Methoden in einem Projekt, um so unwartbarer wird es. Das Debugging wird dadurch auch nicht einfacher. Dann lieber zwei Tasten mehr drücken beim Coden und alles explizit deklarieren.

    Magische Methoden können aber natürlich auch nützlich sein – beispielsweise wenn man ein Auto-Escaping für View-Variablen bauen will. Da könnte man einfach eine Klasse schreiben, die einfach alle Methodenaufrufe und Get/Set-Zugriffe auf Instanzvariablen abfängt und an den View durchreicht, aber vorher ein Escaping auf die übergebenen Werte durchführt.

    Reply
  5. Dennis: Genau für sowas könntest Du doch wunderbar magische Methoden einsetzen – einfach Zugriffe auf undeklarierte Eigenschaften abfangen und eine Exception werfen 😉

    class Pedantic {
    public function __set($key, $value) {
    throw new Exception(„Tried do set undeclared variable ‚$key'“);
    }
    }

    Reply
  6. @rbq: Klar geht das, aber dieser PHPDoc Typehint ist dann doch nur im aktuellen Scope gültig, soviel ich weiß. Bei der nächsten Verwendung der Var, muss ich es wieder machen, statt einmal zentral.
    Was du Codeanalyse angeht. Da meinte ich auch gar nicht den PHPDoc Teil. Ich will nur herausfinden, ob eine Variable definiert wurde. Das ist einfacher, wenn sie im Kopf als Instanzvariable definiert wird, als wenn sie ad-hoc implizit deklariert wird.

    Reply
  7. Ist ja nicht nur innerhalb von Klassen so .. wie oft sieht man irgendwie ein $array[] = ‚foo‘; ohne, dass die entsprechende Variable vorher als Array deklariert wurde?

    Ich frag mich da immer .. nur weil’s funktioniert, muss ich’s doch nicht auch so machen? Wenn man sich mal ein typsicheres === angewohnt hat – dann läuft vieles anders 🙂

    Reply
  8. @ Dennis .. „PHP meldet den Fehler nicht“, bitte? Fehler? Membervariablen hast du deswegen noch lange keine zwei – du kannst auch außerhalb (nochmal) $bar = 1; definieren .. stört auch keinen .. und Fehler isses immer noch keiner 🙂

    Reply
  9. Warum muss in dem Beispiel von oben überhaupt eine Methode zum setzten der Variable existieren?
    Die Eigenschaft steht ja auf Public, dann kann man die Eigenschaft abfragen und setzten wie man will.

    Methoden zum setzten und abfragen von Eigenschaften finde ich nur Sinnvoll, wenn beim setzten die Werte geprüft werden müssen o.ä. und dann sollte der Zugriff für die Eigenschaft sowieso auf private stehen.

    Reply
  10. @Timo: Das würde ich so nicht sagen. Auch wenn es nur eine einfache Zuweisung ist, kann es Sinn machen. Wenn du beispielsweise später noch eben eine solche Prüfung hinzufügen willst, brauchst du andere Klassen nicht ändern, sondern veränderst einfach die entsprechende Methode. Vorausgesetzt, die anderen Klassen nutzen auch alle die entsprechenden Methoden.

    Getter und Setter sind auf jeden Fall vorteilhaft. Und selbst bei 10 oder mehr Attributen dauert es Dank Copy&Paste nur wenige Minuten, alle Setter und Getter anzulegen. Oder die IDE kann das sogar automatisch.

    Reply
  11. Instanzvariablen sollten sowieso nie public sein. Die Kapselung von Daten und „Information Hiding“ ist ein zentraler Bestandteil von OO. Die Initialisierung der Variablen sollte im Konstruktor erfolgen. Das ist zwar lästig, aber sauber.

    class Foo
    {
    private $var = null;

    function __construct() {
    $this->var=““;
    }
    public function setVar( $value ) {
    $this->var = $value;
    }
    public function getVar {
    return $this->var;
    }
    }

    Reply
  12. Äh ja…ist eine oft grausame und fruchtlose Diskussion, aber:
    Sobald ich ein (primitives) get* oder set* sehe, weiß ich, dass ich etwas falsch mache. Mit ‚primitiv‘ meine ich Getter/Setter, die nur Werte setzen oder lesen.

    Bei Objekten geht es vor allem um Zustände und Verhalten (state/behaviour). Und die regeln sie selbst. Also darf ich auch keine solchen Setter haben.
    Sonst habe ich nur primitive Datenklassen und mache COP statt OOP.

    Ansonsten zum Artikel: Ja, ist halt der Nachteil mit dem man leben muss, wenn man eine leicht erlernbare Sprache anwendet; das solcher Pfusch möglich ist. Aber jede Sprache hat solche schwachen Momente, mal mehr, mal weniger.

    Schlimm finde ich es nicht, dass so etwas nicht mit verhindert werden kann (das Beispiel mit __get ist ja selbst wieder Pfusch).

    Ist halt wie im richtigen Leben: gibt es ein Problem und findet man die Ursache, kriegt der Azubi, der’s verbockt hat eins auf die Finger.
    😉

    Cheers!

    Reply
  13. @juhu
    warum sollte ich die Variablen im Konstruktor initialisieren statt direkt bei der Deklaration? IMO ist

    class Foo {
    private $bar = “
    […]
    }

    eindeutiger als

    class Foo {
    private $bar = null;
    public function __construct() {
    $this->bar = “;
    }
    […]
    }

    Reply
  14. @Juhu: Warum sollten Instanzvariablen nie public sein?
    Wenn ich eine Klasse verwende in der Ich viel einstellen muss, finde ich es ganz schön nervig jedes mal eine Set-Methode aufzurufen, anstatt dem werden der Eigenschaft direkt zuzuweisen.

    $myClass = new Foo();
    $myClass->bar = ‚Hello World‘,

    Was habe ich dadurch für ein Nachteil?

    Reply
  15. @danielj
    von einer Klasse instanziert man meist mehrere Objekte. Der Konstruktor wird genau 1 mal bei dieser Instanzierung aufgerufen. Von daher ist es genau der richtige Zeitpunkt um Instanzvariablen zu initialisieren, d.h. einmal pro Objekt.

    Reply
  16. @Timo
    öffentliche Instanzvariablen können von von außen ohne jegliche Kontrolle mit Werten versorgt werden. Genau das wollte man mit der Objektorientierung verhindern, indem man die Daten kapselt und lesenden und schreibenden Zugriff auf Instanzvariablen nur über Methoden anbietet.

    $myClass = new Foo();
    $myClass->zahl = „Hello World“; //fail

    $myClass->setZahl(„Hello World“); //die Methode setZahl könnte die fehlerhafte Zuweisung abfangen

    wie schon gesagt, OO ist manchmal nervig, aber die Konzepte sind durchdacht.

    Reply
  17. Bitte noch mal die wichtigsten Prinzipien von OOP durchlesen.
    Am besten jeden Tag vor dem Frühstück und vor dem Schlafen gehen.
    Das ist kein Witz, sondern sehr sehr ernst gemeint.

    Was juhu schreibt ist nämlich essentiell.
    Die Kapselung ist das oberste aller dieser Prinzipien, dagegen verblassen alle anderen, vor allem die fast unnötige Vererbung (Delegation ist das Zauberwort).

    @Timo
    Weil das Objekt sonst schon mit irgendwelchen Zuständen und Werten belegt wäre, bevor es überhaupt erzeugt wird. Passiert das im Konstruktor, wird das Objekt als Ganzes korrekt erstellt, in dem es selbst diese Sachen festlegt, das ist wieder Kapselung und vor auch Verhalten.
    Stichwort zum weiterlesen: Whole Object.

    Lasst eure Objekte nicht zu Datenhalden verkommen!
    Im real life seid ihr ja auch keine Nummern sondern ihr lebt und entscheidet selbst, wie ihr etwas tut.
    Oder?
    😉

    Cheers!

    Reply
  18. @zampano: Wenn Du auf die gängigen Standarddefinitionen zu OOP verweist, bitte auch bedenken, daß diese sehr allgemein gehalten und nicht sprachspezifisch sind. Es bring absolut nichts, sich sklavisch an eine akademische Definition von X zu halten, wenn man in einer Umgebung arbeitet, in der diese nicht sinnvoll anwendbar ist.

    Im genannten Beispiel der Wertinitialisierung von Instanzvariablen exklusiv im Konstruktor sei anzumerken, daß es in PHP, und davon sprechen wir hier, schlicht egal ist, ob die Variablen im Konstruktor oder bei ihrer Deklaration initialisiert werden. Die Aussage, das Objekt habe dann vor seiner Erzeugung schon einen Zustand, zählt nicht, denn in PHP gibt es eben halt vor der Erzeugung kein Objekt, nur eine Klasse, die in PHP kein Objekt ist.

    PHP ist weder Javascript noch Smalltalk 😉

    Ob dann also beim „new“ die Werte im Konstruktor oder bei Deklaration initialisiert werden, ist in der Praxis irrelevant. Von der Les- und Wartbarkeit her macht es aber ganz viel Sinn, das in der Definition abzufrühstücken, denn da steht alles beieinander. Zudem ist dies dann einheitlicher, wenn man außerdem noch statische Klasseneigenschaften hat, die initiale Werte erhalten sollen – da für diese der Konstruktor nie ausgelöst wird, hat man selbstverständlich nur bei der Deklaration überhaupt diese Möglichkeit.

    Ausnahme ist natürlich, wenn es zur Initialisierung von Instanzvariablen komplexer Operationen bedarf – genau dafür ist der Konstruktor geeignet (unter Anderem).

    Reply
  19. @juhu: Deswegen sagte ich Setter-Methoden sind sinnvoll wenn die Werte geprüft werden müssen.
    Nehmen wir mal die Klasse PHPMailer. Dort gibt es die Eigenschaft „CharSet“.
    Klar kann man als Benutzer der Klasse reinschreiben was man will, aber bekommt man dann keine Anständige bzw. eine E-Mail mit falschem Codierung.
    Da ist man dann selber Schuld.

    Vielleicht sollte an dieser Stelle das Konzept von C# übernommen werden. Das finde ich immer noch am besten.

    Reply
  20. @Markus
    Na, so einfach ist es dann doch nicht. Es spielt keine Rolle, welche Sprache ich benutze und wie die damit umgeht.

    Das weiß ich schon, das man sich nicht sklavisch daran halten sollte. Allerdings kann man einige Prinzipien zwar umgehen oder als akademisches Zeug abstempeln, nur macht man dann eben kein wirkliches OOP mehr, eher COP (Class Oriented P.), was leider oft die Regel ist.

    Und nur weil es in PHP geht, heiß das doch nicht, das man es machen sollte. Nicht alles was machbar ist, ist auch sinnvoll.
    Abgesehen davon kann sich das noch ändern, und dann schreibe ich alles um?

    Ähm, und wieso sollte das Besprochene in PHP nicht sinnvoll anwendbar sein?
    Guckst du…

    Wenn es irrelevant ist, ob ein Attribut im Constructor oder bei der Deklaration initialisiert wird, dann ist dieser Wert ebenso irrelevant. Und von denen rede ich nicht.

    Vor allem geht um dies hier:

    $c = new Customer();
    $c->setFirstname(‚John‘);
    $c->setPreffered(1);
    $c->doSomething();
    $orm->save($c);

    So, was ist das für ein Customer? Einer ohne Nachnamen und Adresse, in welchem Status ist er und warum darf er ein Premiumkunde sein? Hat er alle Eigenschaften bekommen um ihn als Customer im weiteren Verlauf ansprechen und auf seine Eigenschaften zugreifen zu können? Und wieso darf ich ihn speichern in dem Zustand??

    Wieso erzeugt man ein Objekt, das noch nicht „fertig“ ist?
    Will man ernsthaft damit im weiteren Verlauf arbeiten?

    Die Umgehung solcher Prinzipien macht vielleicht weniger Mühe, führt aber zu fehleranfälligen und unverständlichem Code und jede Menge Seiteneffekten bei komplexeren Anwendungen.
    Die Beispiele hier waren ja Pipifax, mach das aber mal bei einer komplexen Anwendung. Die haut dir das Zeug später um die Ohren, wenn du nicht kapselst, Informationen versteckst und Kernkonzepte und Verhalten nicht explizit gestaltest.

    Reply
  21. @Don: So ganz verstehe ich nicht, worauf du mit deinem Bsp. hin willst. Es geht doch darum, ob man den Nachnamen im Konstruktor oder schon in der Definition setzen (egal ob es hier Sinn mach oder nicht). Das verhalten deines Beispieles, wäre bei beiden Versionen gleich, oder?
    Es wäre also eher sowas wie:
    class Customer
    {
    private $isPreffered = false;
    // …
    }

    gegen

    class Customer
    {
    private $isPreferred;

    public function __construct( )
    {
    $this->isPreferred = false;
    }
    }

    Oder verstehe ich da was falsch? Dass alle „Pflichtfelder“ mit der Initialisierung gefüllt sein sollen, da stimme ich dir voll und ganz zu.

    PS: Sorry falls die Beispiele ein wenig hinken.

    Reply
  22. @Timo

    Um z.B. einen Adresse zu ändern, könnte man nur eine neue Straße angeben.
    $c->setStreet(…);

    Aber man kann auch etwas machen:
    $c->changeAddress(…);

    Damit kriegst du auf Methodenebene einen in sich abgeschlossenen Vorgang.
    Und musst nicht wissen, das man bei einer Adressänderung noch weitere 5 Methoden aufrufen muss, die vielleicht auch noch mehr machen.

    Ziehst du von Hausnummer 15 in 17 ändert sich nicht nur deine Hausnummer, sondern es ändert sich deine Adresse!
    Weil das „Konzept“ Adresse in den meisten Anwendungen (nicht nur Software) als Ganzes betrachtet wird.

    Das ist die angesprochene Kapselung von Informationen.

    Reply
  23. @Nils
    Es ist ein Kreuz mit simplifizierten Beispielen, da kannst du bestimmt ein Lied davon singen 😉
    Manche Dinge lassen sich halt eben nicht in ein paar Worten erklären. (Und wir beide sollten preferred richtig schreiben lernen…)

    Aber zum Thema:
    In deinem Beispiel ist beides äquivalent, das sagte Markus ja auch schon und (widerwillig) stimme ich dem auch zu.
    Abgesehen davon, dass ich mit PreferredCustomer oder so arbeiten würde.

    Preferred ist aber ein Status des Objekts. Und wir erinnern uns: Objects are all about state and behaviour.

    Und wenn dieser änderbar sein soll, dann kann er nur vom Objekt selbst geändert werden oder von einem anderen Objekt, dass dazu befähigt ist. Ein Kunde macht sich ja nur selten selbst zum Premiumkunden, nicht wahr?

    Deshalb sind die meisten der in Beispielen gezeigten Setter böse. Nicht vom technischen Standpunkt aus, sondern vom konzeptionellen; und Letztere entscheidet in einer Geschäftsanwendung.
    Im Kommentar zu Timo habe ich ein Beispiel dazu.

    Analog zu preferred wäre ein Beispiel, dass dem Kunden ein Punktewert zugewiesen wird und es daraufhin zu einer Aufwertung zum Premiumkunden kommt.
    Oder aber ich habe eine explizite Methode z.B. in der Anwendung des Kundenservice, der beliebige Kunden zu Premiumkunden machen kann (z.B. weil es mein Vetter ist).

    Es ist in beiden Fällen eine Kapselung der Informationen, die nicht bloß Werte sind, sondern eine explizite Bedeutung haben und oft einiges entweder nach sich ziehen oder Vorbedingungen erfüllt sein müssen.

    Ich merke aber langsam auch, dass dies ein viel zu vielschichtiges Thema für einen Blogkommentar ist.

    Reply
  24. @Don: Da muss ich dir recht geben 🙂 Viel zu vielschichtig. Aber ich glaube, dass wir gar nicht weit mit unseren Meinungen auseinander liegen, ich würde zumindest fast alles unterschreiben, was du geschrieben hast.
    Falls du Interesse hast, was über böse Setter zu schreiben, dann veröffentliche ich das gerne.

    Reply

Leave a Comment.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen