borisbaer: Wiederholung der Arguments von PHP-Methods bei Beerbung abstrakter Klassen vermeiden

Hallo zusammen,

ich habe eine abstrakte PHP-Klasse namens Model, die CRUD-Methoden beinhaltet.

Vor allem die read-Methode hat mehrere Arguments:

protected static function read(

	array|string $condition = [],
	int $mode = PDO::FETCH_DEFAULT,
	string $sort = '',
	int|string $limit = null,

): array|bool|object

Wie ihr seht, sind diese optional.

Zur Festlegung des Tabellen-Namens sowie der zu lesenden Tabellen-Spalten habe ich die Klasse Model als abstrakte Klasse definiert. Andere Klassen, die jene Model-Klasse beerben, müssen daher zwangsläufig durch die Funktion getTableName und getAttributes (für die Tabellen-Spalten) die jeweiligen Namen generieren.

Jedenfalls geht es darum, dass, wenn bspw. die read-Method in einer anderen Klasse verwendet werden soll, ich die Arguments der Eltern-Klasse – in diesem Fall der Model-Klasse – erneut eingeben muss. Wenn diese Arguments nicht denen der Method in der Eltern-Klasse entsprechen, gibt es natürlich eine Fehlermeldung. Das heißt, wenn mehrere Klassen die Model-Klasse beerben bzw. deren Methods verwenden, muss ich bei jeder Änderung der Arguments der Eltern-Methods die Arguments bei allen anderen Klassen auch verändern. Ich hoffe, es ist klar, was ich meine.

Welche Möglichkeiten hätte ich denn, das zu vermeiden? In denjenigen Klassen, die die Model-Klasse beerben, möchte ich anstatt

public static function read(

	array|string $condition = [],
	int $mode = PDO::FETCH_DEFAULT,
	string $sort = '',
	int|string $limit = null,

): array
{
	return parent::read( $condition );
}

bspw. einfach nur

public static function read(): array
{
	return parent::read( $condition );
}

schreiben können.

Oder womöglich wäre sogar ein ganz anderer Ansatz viel besser.

Wenn es um die Abhängigkeiten von PHP-Klassen geht, kann man ja auf einen Dependency Injection Container zurückgreifen. Gibt es so etwas Ähnliches auch bei geerbten Methods abstrakter Klassen?

Grüße
Boris

akzeptierte Antworten

  1. Hallo borisbaer,

    soweit ich weiß, musst Du die Methodensignatur kopieren.

    Woher soll PHP sonst wissen, was $condition ist?

    Es kann nicht grundsätzlich alle Parameter der Methode aus der Superklasse automatisch übernehmen. Es kommt durchaus vor, dass man eine Methode überschreibt und ganz bewusst eine andere Signatur wählt. Woher soll PHP wissen, was Du willst?

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Ergänzung:

      Das heißt, wenn mehrere Klassen die Model-Klasse beerben bzw. deren Methods verwenden, muss ich bei jeder Änderung der Arguments der Eltern-Methods die Arguments bei allen anderen Klassen auch verändern.

      Und das ist GUT so. Denn andernfalls würdest Du eventuell gar nicht merken, dass da Erben sind, die ihr Erbe unordentlich behandeln.

      Andererseits ist es nicht gut, eine Basisklasse ständig zu ändern. Eigentlich sollte man sich einmal Gedanken gemacht haben, und dann das Framework stabil halten. Damit man eben nicht ständig ändern muss.

      Die Frage ist auch, ob es wirklich nötig ist, read() zu überschreiben. Diese Methode sieht sehr generisch aus, und die für's konkrete Model relevanten Dinge hast Du doch in abstrakten Methoden untergebracht, die zu überschreiben sind. Wenn Du der read-Methode keine Funktionalität hinzufügen musst, kannst Du sie ggf. doch auch unverändert erben.

      Und wenn es bei einigen Models darum geht, die Condition oder die Sortierung oder den PDO Mode vorzudefinieren, dann könnte man auch Methoden wie "getDefaultCondition" oder "getPDOMode" schreiben, die eine Modelklasse überschreiben kann, aber nicht muss.

      Falls Du read so aufrufen willst, dass nur der erste und dritte Parameter übergeben werden und der zweite und vierte Parameter ihren Defaultwert behalten, kannst Du das so tun:

      $model->read(
         condition: [ "foo" => "bar", "hui" => 17 ],
         sort:      "descending"
      );
      

      Oder wie auch immer deine Bedingungen aussehen. Der Trick ist jedenfalls, den Parameternamen mit einem Doppelpunkt vor den Wert zu schreiben. Natürlich musst Du das nicht auf 4 Zeilen verteilen. Das habe ich wegen der Lesbarkeit im Forum gemacht.

      Rolf

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

        Andererseits ist es nicht gut, eine Basisklasse ständig zu ändern. Eigentlich sollte man sich einmal Gedanken gemacht haben, und dann das Framework stabil halten. Damit man eben nicht ständig ändern muss.

        Ich verstehe. Dann gibt es da kein Workaround, sondern man muss eben gut im Voraus planen.

        Die Frage ist auch, ob es wirklich nötig ist, read() zu überschreiben. Diese Methode sieht sehr generisch aus, und die für's konkrete Model relevanten Dinge hast Du doch in abstrakten Methoden untergebracht, die zu überschreiben sind. Wenn Du der read-Methode keine Funktionalität hinzufügen musst, kannst Du sie ggf. doch auch unverändert erben.

        Könntest du erläutern, an welcher Stelle read() überschrieben wird? Ich habe sie z.B. anpassen müssen, weil ich noch ein optionales LIMIT einbauen wollte, das als Parameter übergeben werden kann. Dann musste ich bei allen Erben das zusätzliche Argument einfügen. So kam mir die Frage, ob ich das nicht irgendwie umgehen kann, falls ich bei read() mal ein Argument ergänzen will.

        Und wenn es bei einigen Models darum geht, die Condition oder die Sortierung oder den PDO Mode vorzudefinieren, dann könnte man auch Methoden wie "getDefaultCondition" oder "getPDOMode" schreiben, die eine Modelklasse überschreiben kann, aber nicht muss.

        Wie genau würde das denn funktionieren? Wenn ich eine abstrakte Methode in der Eltern-Klasse definiere, dann muss ich diese doch beim Erben ebenfalls zwangsweise definieren. Kann man da Optionalität einstellen?

        Falls Du read so aufrufen willst, dass nur der erste und dritte Parameter übergeben werden und der zweite und vierte Parameter ihren Defaultwert behalten, kannst Du das so tun:

        $model->read(
           condition: [ "foo" => "bar", "hui" => 17 ],
           sort:      "descending"
        );
        

        Oder wie auch immer deine Bedingungen aussehen. Der Trick ist jedenfalls, den Parameternamen mit einem Doppelpunkt vor den Wert zu schreiben. Natürlich musst Du das nicht auf 4 Zeilen verteilen. Das habe ich wegen der Lesbarkeit im Forum gemacht.

        Named Arguments helfen mir hier leider nicht, denn bei folgendem Code

        public static function read(
        
        	array|string $condition = [],
        	int $mode = 0,
        	string $sort = '',
        	int|string $limit = null,
        
        ): bool
        {
        return parent::read( $condition, PDO::FETCH_BOUND );
        }
        

        kann ich zwar

        parent::read( condition: $condition, mode: PDO::FETCH_BOUND )
        

        schreiben, aber die Arguments bei public static function read( … muss ich ja trotzdem alle hinschreiben. Um die geht es mir ja letztlich.

        Danke für den Input!

        Grüße
        Boris

        1. Hallo borisbaer,

          ...weil ich noch ein optionales LIMIT einbauen wollte,
          Dann musste ich bei allen Erben das zusätzliche Argument einfügen.

          Aber eben nur dann, wenn die Erben read() überschreiben.

          In deinem Beispiel überschreibst Du read, um den Mode vorzugeben.

          Wie wär's hiermit (ich schreibe "...", wenn ich nicht all deinen Code abschreiben will):

          abstract class Model {
             public static function read(..., $mode=null, ...) {
                if ($mode === null) {
                   $mode = static::get_mode();
                }
             }
             protected static function get_mode() { return PDO::FETCH_DEFAULT; }
          }
          
          class XyzModel extends Model {
             protected static function get_mode() { return PDO::FETCH_BOUND; }
          }
          

          Jetzt musst Du read gar nicht überschreiben, kannst aber trotzdem den Mode verändern.

          Eine andere Alternative wäre eine alternative Parameterübergaben. Die hast Du Dir allerdings schon erschwert, weil Du für die Conditions ein Array akzeptierst. Aber ich denke, Conditions hast Du eigentlich immer.

          Du könntest für Parameter 2 den Typ als array|int festlegen. Bekommst Du ein Array, ist das ein Array mit möglichen Parametern. Das ist dann natürlich nicht typsicher, insofern also ein Rückschritt. Aber man könnte dann so aufrufen:

          XyzModel::read("Hugo=3", [ "mode" => PDO::FETCH_BOUND, "limit" => 5 ]);
          

          Oder wie auch immer Du Deine Conditions aufschreibst. Wenn Du im $mode Parameter ein Array findest, dann guckst Du, ob es Keys für deine Parameter enthält, und es hindert Dich niemand, da jederzeit einen Key hinzuzufügen.

          So richtig schwierig ist das eigentlich nur, weil Du alles statisch machst. Wären das Instanzmethoden und würdest Du für die Aufrufe ein Model-Objekt mit new erzeugen, dann könntest Du Dinge wie fetch-mode oder limit als Properties anbieten, nach dem new Aufruf setzen und dann read nur mit den Conditions aufrufen.

          Rolf

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

            vielen Dank für die verschiedenen Vorschläge!

            So richtig schwierig ist das eigentlich nur, weil Du alles statisch machst. Wären das Instanzmethoden und würdest Du für die Aufrufe ein Model-Objekt mit new erzeugen, dann könntest Du Dinge wie fetch-mode oder limit als Properties anbieten, nach dem new Aufruf setzen und dann read nur mit den Conditions aufrufen.

            Ja, ich tendiere auch fast dazu, das Konzept mit den statischen Methoden zu überdenken.

            Grüße
            Boris

  2. Hello,

    würde da eventuell use helfen können?

    Das muss man bei anonymen Funktionen doch auch benutzen, um den äußeren Kontext innen bekannt zu machen.

    Oder liege ich da jetzt vollkommen daneben?

    Glück Auf
    Tom vom Berg

    --
    Es gibt soviel Sonne, nutzen wir sie.
    www.Solar-Harz.de
    S☼nnige Grüße aus dem Oberharz