T-Rex: Klassen constante vom Kind benutzen um eigene Constante zu definieren

Moin,

ich möchte, dass folgendes oder ein ähnliches Konstrukt funktioniert:

class cTestParent
{
	const ID = self::TABLENAME."_ID";
}

class cTestChild extends cTestParent
{
	const TABLENAME = "test";
}

$objChild = new cTestChild();
echo $objChild::ID;

Im Parent werden Datenbankfelder definiert, die für mehrere erbende Klassen gleich sind und sich nur im Tabellennamen ändern. Der Tabellenname soll nur in den Childs definiert werden. Am Ende möchte ich mit $objChild::ID auf die Tabellenfelder zugreifen. Und NUR SO. Ein $objChild->ID oder ein $objChild::$ID ist nicht möglich (um große Erklärungen zu ersparen, dass ist einfach so).

Obiges Beispiel bringt den Fehler "Exception: Undefined class constant 'self::TABLENAME'". Mache ich ein sonst TABLENAME = self::TABLENAME; dazu, dann bekomme ich den Fehler: "Exception: Cannot declare self-referencing constant 'self::TABLENAME'".

Natürlich kann ich jedes neue Feld in alle ableitende Klassen reinpacken. Im Moment sin das schon 4 - ergo muss ich 4 Dateien aufmachen und die neuen Felder da reinpacken. Es können aber locker noch an die 20 oder sogar noch mehr werden. Ergo suche ich eine elegante Lösung die Felder nur einmal definieren zu müssen.

Gruß fauler x

  1. Hallo,

    nur zur Klarstellung: Ich habe vermutlich nicht wirklich verstanden, was du erreichen willst. Ich habe aber den Eindruck, dass dein Ansatz mehrere Denkfehler hat. Mindestens zwei.

    Wenn du eine Variable oder Konstante für die Klasse definierst (also mit der Doppeldoppelpunkt-Notation), dann existiert sie genau einmal für alle Instanzen der Klasse. Das widerspricht deiner Beschreibung, nach der die Children (nicht Childs) jedes ihren eigenen Tabellennamen haben sollen. Du möchtest quasi mehrere Kinder haben, aber sie sollen sich alle einen gemeinsamen Personalausweis teilen.

    Der zweite Knackpunkt ist ein Henne-Ei-Problem. Du kannst bei der Definition der Basisklasse nicht auf Eigenschaften von abgeleiteten Klassen oder gar deren Instanzen zugreifen, weil die zu dem Zeitpunkt noch gar nicht existieren bzw. bekannt sind. Oder haben deine Eltern dir bei der Geburt den Vornamen gegeben, den du später auch deinem Erstgeborenen geben würdest?

    Am Ende möchte ich mit $objChild::ID auf die Tabellenfelder zugreifen. Und NUR SO. Ein $objChild->ID oder ein $objChild::$ID ist nicht möglich (um große Erklärungen zu ersparen, dass ist einfach so).

    Wenn du prinzipbedingte Einschränkungen hast, kann es nützlich sein, diese zu erklären. Dann kann man nämlich beim Entwickeln einer möglichen Lösung darauf achten.

    Obiges Beispiel bringt den Fehler "Exception: Undefined class constant 'self::TABLENAME'".

    Natürlich. In der Klasse ist ja keine Konstante TABLENAME definiert.

    Mache ich ein sonst TABLENAME = self::TABLENAME; dazu, dann bekomme ich den Fehler: "Exception: Cannot declare self-referencing constant 'self::TABLENAME'".

    Auch klar. Du willst bei der Deklaration der Konstanten bereits auf ihren Wert zugreifen. Das geht nicht. Du kannst nicht beim Schreiben eines Briefes schon das Zustelldatum im Text nennen. Das ergibt sich erst später.

    Natürlich kann ich jedes neue Feld in alle ableitende Klassen reinpacken.

    Geht's um neue Felder (Eigenschaften) in den Kind-Instanzen? So wie ich dich verstanden habe, sollte die Kindklasse eine bestimmte Eigenschaft haben, die beim Erzeugen der Instanz mit einem Wert belegt wird.

    Aber wie gesagt: Ich werde aus deiner Beschreibung noch nicht schlau - es ist daher fraglich, ob ich dir überhaupt weiterhelfen kann.

    May the Schwartz be with you
     Martin

    --
    Theorie ist, wenn eigentlich jeder weiß, wie's gehen müsste, und es geht doch nicht.
    Praxis ist, wenn's geht, obwohl es keiner so richtig versteht.
    Bei uns sind Theorie und Praxis vereint: Nichts geht, und keiner weiß, warum.
    1. Du möchtest quasi mehrere Kinder haben, aber sie sollen sich alle einen gemeinsamen Personalausweis teilen.

      Exakt. Ich dachte bei Vererbung wäre das der Sinn? Würde ich auf das Parent Element zugreifen cTestParent::TABLENAME und es wurde ein Fehler geworfen werden, würde ich das verstehen. Da ich aber auf die Kinder gehe, verstehe ich es nicht, da es mittels extend eine Verbindung zum Parent geben müsste. Auch müsste der Parent das Child kennen.

      Die Lösung die ich hatte mit const TABLENAME = self::TABLENAME wird in anderen Foren empfohlen und als wertvollste Antwort ausgezeichnet: https://stackoverflow.com/questions/10368620/abstract-constants-in-php-force-a-child-class-to-define-a-constant

      Das wäre eine Lösung mit der ich sehr zufrieden wäre. Wieso funktioniert die bei mir nicht? Mein PHP ist irgendwas mit 7.4.1 oder so.

      Gruß 7.7.83 T-Rex

      1. Hallo T-Rex,

        const TABLENAME = self::TABLENAME;

        Brrrr - lass das. Das crasht mit "Cannot declare self-referencing constant", sobald Du in der abstrakten Klasse irgendwas mit self tust.

        Rolf

        --
        sumpsi - posui - obstruxi
  2. Hallo T-Rex,

    das Problem ist, dass Klassenkonstanten statische Elemente sind, d.h. nur einmal pro Klasse existieren. Und sie werden festgelegt, wenn die Klasse deklariert wird.

    Du möchtest aber eine ID, die einerseits in der Superklasse deklariert ist, andererseits aber pro abgeleiteter Klasse einen unterschiedlichen Wert hat. Das geht mit Klassenkonstanten nicht. In C++ könnte man möglicherweise mit Templates etwas erreichen - das hilft Dir in PHP natürlich nichts.

    Ich sehe nur die Möglichkeit, zur Ausführungszeit etwas zu machen. Zur Deklarationszeit nicht. Um Schreibarbeit zu sparen, kann man einen Trait verwenden (das ist ein einbindbarer Baustein für Klassen, der statischen Eigenschaften, statische Methoden und Instanzmethoden enthalten darf). Da PHP auch keinen statischen Initializer kennt, muss das der Konstruktor der Objekte anstoßen. Man kann das aber an einer Stelle, in der Superklasse, erledigen.

    trait ColumnNames 
    {
        public static $ID;
        public static $COLX;
        public static $COLY;
        
        public static function initColumnNames() 
        {
            echo "Initialisiere " . get_class() . "\n";
            $t = self::TABLENAME;
            self::$ID   = $t . "_ID";
            self::$COLX = $t . "_COLX";
            self::$COLY = $t . "_COLY";
        }
    }
    
    class AbstractTable 
    {
        public function __construct() 
        {
            echo "Erzeuge " . get_called_class() . "\n";
            if (method_exists($this, "initColumnNames") && !($this::$ID))
               $this::initColumnNames();
    // ODER - um bei vergessenem Trait hart zu crashen, als Einzeiler:
            if (!($this::$ID)) $this::initColumnNames();
        }
    }
    
    class FooTable extends AbstractTable 
    {
        use ColumnNames;             // Gimme the Trait!
        const TABLENAME = "Foo";
    }
    
    class BarTable extends AbstractTable 
    {
        use ColumnNames;             // Gimme the Trait!
        const TABLENAME = "Bar";
    }
    
    $f1 = new FooTable();
    $f2 = new FooTable();
    $b = new BarTable();
    
    echo "ColumnNames: " . ColumnNames::$ID . "\n";   // Leer
    echo "Foo: " . FooTable::$ID . "\n";              // Foo_ID
    echo "Bar: " . BarTable::$ID . "\n";              // Bar_ID
    

    Die eigentlichen Implementierungsklassen müssen lediglich den Trait einbinden und TABLENAME deklarieren. Die statischen Properties im Trait werden durch den use in der einbindenden Klasse angelegt, das ist Voraussetzung, damit diese Lösung funktioniert.

    Den Rest übernehmen Trait und Superklasse. Ohne die Echos ist es eigentlich recht kompakt. Die Abfrage, ob die Initialisierung bereits ausgeführt wurde, mache ich im abstrakten Konstruktor, um bei vorhandener Initialisierung den Methodenaufruf in den Trait zu sparen.

    Fragen? Anregungen? Vorschläge?

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Ingrid,

      soeben gelernt: statt den Klassennamen in eine Variable zu holen, kann man statische Elemente auch über $this referenzieren: $this::$ID liest die statische Variable $ID von der Klasse des $this-Objekts, d.h. damit kann die abstrakte Superklasse die $ID-Variable der jeweils erbenden Subklasse erreichen ohne über get_called_class() erstmal deren Namen ermitteln zu müssen!

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Hallo Ingrid,

        Hallo Hilde,

        $this::$ID

        Diese Schreibweise kann ich aber nicht gebrauchen. Es muss $this::ID sein. Gibt es nicht eine magische Funktion die aufgerufen wird, wenn man auf Konstante zugreift? Ich meine, dass gibt es für get und set doch auch...

        Gruß Ingrid

        1. Hallo T-Rex,

          Es muss $this::ID sein.

          Nicht so feste, du trittst ein Loch in den Estrich 😉

          Dann hast Du wohl Pech. Es gibt "magic constants", aber das bezieht sich auf __FILE__, __LINE__ und Co. Die vorhandenen magischen Methoden von PHP tun allesamt andere Dinge.

          <expertentipp level="master">Lade Dir den Sourcecode von PHP herunter und bau die entsprechende magische Methode ein, dann schick einen Pullrequest dafür.</expertentipp>

          Ja. Und für uns Normalsterbliche: Wieso MUSS es denn eine Konstante sein, warum darf es kein statisches Property sein? Das kannst Du übrigens auch nicht per magic method bedienen, eine Methode __getStatic wurde wohl schon mehrfach erbeten, aber bisher nicht eingebaut. Zum Beispiel: https://bugs.php.net/bug.php?id=62860 und https://bugs.php.net/bug.php?id=45002.

          Vielleicht wäre es hilfreich, einen Schritt zurückzutreten und sich das Gesamtkonstrukt anzuschauen, für das Du das brauchst. Kann man das Problem ggf. anders lösen? Welches Problem willst Du lösen? Kann Dir vielleicht ReflectionClass helfen?

          Rolf

          --
          sumpsi - posui - obstruxi
        2. Tach!

          Gibt es nicht eine magische Funktion die aufgerufen wird, wenn man auf Konstante zugreift?

          Eine Konstante, die sich nach Gegebenheit ändert, ist keine Konstante.

          Die Möglichkeit der Self-Referenz mit Überschreiben in erbenden Klassen wurde anscheinend im Laufe von PHP 7.1.x entfernt. In der PHP-Sandbox kann man es noch für 7.1.0 verwenden, aber 7.1.29 wirft einen Fatal Error.

          dedlfix.

  3. Ist es vielleicht möglich die Constenten Definition aus zu gliedern?

    class cTestChild extends cTestParent { require_once "ChildConstants.php" }

    Dann müsste ich die Konstanten nur dort definieren und könnte die Datei überall dort reinhängen wo sie gebraucht wird.

    Gruß um die Ecke denkender T-Rex

    1. Tach!

      Ist es vielleicht möglich die Constenten Definition aus zu gliedern?

      Was ergab dein Versuch?

      class cTestChild extends cTestParent
      {
      	require_once "ChildConstants.php"
      }
      

      Ungültige Syntax. include kann zwar innerhalb von Methoden stehen, aber nicht außerhalb in einer Klasse.

      dedlfix.

      1. Ja ... gibt es da eine ähnliche Möglichkeit die funktioniert?

        1. Tach!

          Ja ... gibt es da eine ähnliche Möglichkeit die funktioniert?

          Da fällt mir eval() ein, wenn es aus PHP heraus gehen soll, oder Dateitemplates, die im Editor, in der IDE oder in einem Build-Tool die endgültige Datei erstellen. Aber schön geht anders. Bei solcher Dynamic beraubt man sich des Toolings in den IDEs. Fehler, die eine IDE nicht aufzeigen kann, sind zur Laufzeit schwerer zu finden.

          dedlfix.

          1. Das war eine gute Idee !! Ja eval ist das absolut letzte was ich benutzen würde, aber in dem Fall. Trotzdem es scheint nicht zu funktionieren:

            $strFields = 'const ID = self::TABLENAME."_ID";';
            class cTestChild extends cTestParent
            {
            	const TABLENAME = "CHILD";		
            	eval( $strFields );
            }
            

            Parse error: syntax error, unexpected 'eval' (T_EVAL), expecting function (T_FUNCTION) or const (T_CONST) in ...

            Oder habe ich etwas falsch gemacht?

            Gruß 😈 T-Rex

            1. Hallo,

              
              > 	$strFields = 'const ID = self::TABLENAME."_ID";';
              > 	class cTestChild extends cTestParent
              > 	{
              > 		const TABLENAME = "CHILD";		
              > 		eval( $strFields );
              > 	}
              > 
              > Parse error: syntax error, unexpected 'eval' (T_EVAL), expecting function (T_FUNCTION) or const (T_CONST) in ...
              
              

              Oder habe ich etwas falsch gemacht?

              ja: Funktionsaufrufe dürfen nicht außerhalb von Methoden in der Klassendeklaration stehen. Und eval() ist ein Funktionsaufruf.

              May the Schwartz be with you
               Martin

              --
              Theorie ist, wenn eigentlich jeder weiß, wie's gehen müsste, und es geht doch nicht.
              Praxis ist, wenn's geht, obwohl es keiner so richtig versteht.
              Bei uns sind Theorie und Praxis vereint: Nichts geht, und keiner weiß, warum.
            2. Tach!

              Bitte zeichne Code als Code aus.

              	$strFields = 'const ID = self::TABLENAME."_ID";';
              	class cTestChild extends cTestParent
              	{
              		const TABLENAME = "CHILD";		
              		eval( $strFields );
              	}
              
              Parse error: syntax error, unexpected 'eval' (T_EVAL), expecting function (T_FUNCTION) or const (T_CONST) in ...
              

              Ich meinte, dass die ganze Klasse als String eval()t wird. eval() wird zur Laufzeit ausgeführt und nicht beim Parsen. Es muss also irgendwo stehen, wo ausführbarer Code steht und nicht nur Code, der Dinge definiert.

              dedlfix.

              1. Ja, dass macht diese Möglichkeit wieder zu einer schlechten.

                Das ich nur die Konstanten "Außerhalb" definiere ist das eine, aber die ganze Klasse ... nein!

                Und so muss ich mein komplettes System umbauen - also wirklich alles. Da alles auf Konstanten ausgelegt ist. Werde es jetzt ähnlich wie Rolf auf public static Variablen umbauen. Das ist soooooo viel - kotz.

                Gruß Verstärkungswörter vermeidender T-Rex

        2. Hallo T-Rex,

          gibt es da eine ähnliche Möglichkeit die funktioniert?

          Du könntest Dir einen Codegenerator schreiben, der Dir die PHP Datei generiert. Bzw. einen Makro-Präprozessor verwenden, der Dir das einbaut. https://pecl.php.net/package/PreProcessor

          Oder Du verzichtest auf die Konstanten. Ich fragte ja schonmal: Wieso MUSS es unbedingt eine Konstante sein? Mit static properties kommt man ja weiter.

          Rolf

          --
          sumpsi - posui - obstruxi
          1. Ja dran habe ich auch schon gedacht. Das wäre aber mit Kanonen auf Spatzen schießen. Da ich auch durch Recherche keine Lösung gefunden habe, habe ich mich am Ende an deiner Lösung orientiert.

            Es sieht jetzt wie folgt aus:

            class cEntityToDo extends cEntity
            {
            	const TABLENAME		= "todo";
            	use tEntity;
            	
            	public static $BESCHREIBUNG				= self::TABLENAME."_beschreibung";
            	public static $ERLEDIGT 				= self::TABLENAME."_erledigt";
            ...
            

            In tEntity stehen dann alle Felder die überall gleich sind. Aktuell sind das id, newdate und editdate. Das kann ich jetzt weiter aufblasen in dem ich für Tabellen die ebenfalls gleiche Felder habe weitere traits mache.

            Trotzdem sehr schade, dass ich das mit Vererbung nicht hinbekommen habe 😟.

            Ach ja, ich durfte meinen kompletten CMS Code anpassen. Deswegen wäre mir eine andere Lösung sehr viel angenehmer gewesen, denn das hat mich jetzt den kompletten Tag gekostet. Und das sind nur die Dateien, die mir jetzt aufgefallen sind. Wer weiß welche alten Konstanten noch versucht werden auf zu rufen...

            Gruß self::$T-Rex

            1. Hallo T-Rex,

              dass ich das mit Vererbung nicht hinbekommen habe

              Schade ist das, ja. Da fehlen in PHP wohl einfach die Sprachmittel.

              Aber Du bist ja nicht der einzige, der es nicht hinbekommen hat. Würde es gehen, hätte Dir sicher jemand einen qualifizierteren Tipp geben können als ich alter Autophpidakt.

              Rolf

              --
              sumpsi - posui - obstruxi