PHP – beste Möglichkeit, ein Objekt mit einer großen Anzahl von Parametern und Standardwerten zu initialisieren

Lesezeit: 12 Minuten

Benutzer-Avatar
Tom Auger

Ich entwerfe eine Klasse, die ein hochkomplexes Objekt mit einer Tonne (50+) von meist optionalen Parametern definiert, von denen viele Standardwerte haben würden (zB: $type="foo"; $width="300"; $interactive = false;). Ich versuche herauszufinden, wie ich den Konstruktor und die Instanz-/Klassenvariablen am besten einrichten kann, um Folgendes tun zu können:

  • machen es einfach, die Klasse zu verwenden
  • Machen Sie es einfach, die Klasse automatisch zu dokumentieren (z. B. mit phpDocumentor).
  • Code dies elegant

In Anbetracht des oben Gesagten möchte ich dem Konstruktor nicht eine Menge Argumente übergeben. Ich werde ihm einen einzelnen Hash übergeben, der die Initialisierungswerte enthält, z. $foo = new Foo(array('type'=>'bar', 'width'=>300, 'interactive'=>false));

In Bezug auf das Programmieren der Klasse habe ich immer noch das Gefühl, ich hätte lieber …

class Foo {
    private $_type="default_type";
    private $_width = 100;
    private $_interactive = true;

    ...
}

… weil ich glaube, dass dies die Dokumentationsgenerierung erleichtern würde (Sie erhalten die Liste der Klasseneigenschaften, die den API-Benutzer wissen lässt, mit welchen ‘Optionen’ er arbeiten muss), und es “fühlt” sich wie der richtige Weg an es.

Aber dann stoßen Sie auf das Problem, die eingehenden Parameter im Konstruktor den Klassenvariablen zuzuordnen, und ohne die Symboltabelle auszunutzen, geraten Sie in einen “Brute-Force” -Ansatz, der für mich den Zweck zunichte macht (obwohl ich offen für andere bin Meinungen). Z.B:

function __construct($args){
    if(isset($args['type'])) $_type = $args['type']; // yuck!
}

Ich habe überlegt, eine einzelne Klassenvariable zu erstellen, die selbst ein assoziatives Array ist. Das Initialisieren wäre dann wirklich einfach, zB:

private $_instance_params = array(
    'type' => 'default_type',
    'width' => 100,
    'interactive' => true
);

function __construct($args){
    foreach($args as $key=>$value){
        $_instance_params[$key] = $value;
    }
}

Aber das scheint, als würde ich native Funktionen wie private Klassenvariablen nicht nutzen, und es scheint, als würde die Dokumentationsgenerierung mit diesem Ansatz nicht funktionieren.

Danke, dass Sie bis hierhin gelesen haben; Ich frage hier wahrscheinlich viel, aber ich bin neu in PHP und suche wirklich nur nach dem idiomatischen / eleganten Weg, dies zu tun. Was sind Ihre Best Practices?


Nachtrag (Details zu dieser bestimmten Klasse)

Es ist sehr wahrscheinlich, dass diese Klasse versucht, zu viel zu tun, aber es ist eine Portierung einer alten Perl-Bibliothek zum Erstellen und Verarbeiten von Formularen. Es gibt wahrscheinlich eine Möglichkeit, die Konfigurationsoptionen aufzuteilen, um Vererbung und Polymorphismus zu nutzen, aber es kann tatsächlich kontraproduktiv sein.

Auf Wunsch hier eine Teilauflistung einiger Parameter (Perl-Code). Sie sollten sehen, dass diese Unterklassen nicht sehr gut zugeordnet werden können.

Die Klasse hat sicherlich Getter und Setter für viele dieser Eigenschaften, sodass der Benutzer sie überschreiben kann; Das Ziel dieses Beitrags (und etwas, das der ursprüngliche Code gut macht) ist es, eine kompakte Möglichkeit zum Instanziieren dieser Form-Objekte mit den erforderlichen Parametern bereitzustellen, die bereits festgelegt sind. Es sorgt tatsächlich für sehr gut lesbaren Code.

# Form Behaviour Parameters
        # --------------------------
        $self->{id}; # the id and the name of the <form> tag
        $self->{name} = "webform"; # legacy - replaced by {id}
        $self->{user_id} = $global->{user_id}; # used to make sure that all links have the user id encoded in them. Usually this gets returned as the {'i'} user input parameter
        $self->{no_form}; # if set, the <form> tag will be omitted
        $self->{readonly}; # if set, the entire form will be read-only
        $self->{autosave} = ''; # when set to true, un-focusing a field causes the field data to be saved immediately
        $self->{scrubbed}; # if set to "true" or non-null, places a "changed" radio button on far right of row-per-record forms that indicates that a record has been edited. Used to allow users to edit multiple records at the same time and save the results all at once. Very cool.
        $self->{add_rowid}; # if set, each row in a form will have a hidden "rowid" input field with the row_id of that record (used primarily for scrubbable records). If the 'scrubbed' parameter is set, this parameter is also automatically set. Note that for this to work, the SELECT statement must pull out a unique row id. 
        $self->{row_id_prefix} = "row_"; # each row gets a unique id of the form id="row_##" where ## corresponds to the record's rowid. In the case of multiple forms, if we need to identify a specific row, we can change the "row_" prefix to something unique. By default it's "row_"

        $self->{validate_form}; # parses user_input and validates required fields and the like on a form
        $self->{target}; # adds a target window to the form tag if specified
        $self->{focus_on_field}; # if supplied, this will add a <script> tag at the end of the form that will set the focus on the named field once the form loads.
        $self->{on_submit}; # adds the onSubmit event handler to the form tag if supplied
        $self->{ctrl_s_button_name}; # if supplied with the name of the savebutton, this will add an onKeypress handler to process CTRL-S as a way of saving the form

        # Form Paging Parameters
        # ----------------------
        $self->{max_rows_per_page}; # when displaying a complete form using printForm() method, determines the number of rows shown on screen at a time. If this is blank or undef, then all rows in the query are shown and no header/footer is produced.
        $self->{max_pages_in_nav} = 7; # when displaying the navbar above and below list forms, determines how many page links are shown. Should be an odd number
        $self->{current_offset}; # the current page that we're displaying
        $self->{total_records}; # the number of records returned by the query
        $self->{hide_max_rows_selector} = ""; # hide the <select> tag allowing users to choose the max_rows_per_page
        $self->{force_selected_row} = ""; # if this is set, calls to showPage() will also clear the rowid hidden field on the form, forcing the first record to be displayed if none were selected
        $self->{paging_style} = "normal"; # Options: "compact"

Wir können uns natürlich auf eine längere Debatte über den Programmierstil einlassen. Aber ich hoffe, dass ich es vermeiden kann, zum Wohle aller Beteiligten! Hier (wieder Perl-Code) ist ein Beispiel für die Instanziierung dieses Objekts mit einem ziemlich umfangreichen Parametersatz.

my $form = new Valz::Webform (
            id                      => "dbForm",
            form_name               => "user_mailbox_recip_list_students",
            user_input              => \%params,
            user_id                 => $params{i},
            no_form                 => "no_form",
            selectable              => "checkbox",
            selectable_row_prefix   => "student",
            selected_row            => join (",", getRecipientIDsByType('student')),
            this_page               => $params{c},
            paging_style            => "compact",
            hide_max_rows_selector  => 'true',
            max_pages_in_nav        => 5
        );

  • Das hört sich an, als würde die Klasse zu viel tun. Können Sie näher darauf eingehen, was diese Klasse tun soll, und vielleicht einige weitere oder alle der 50 Eigenschaften auflisten?

    – Gordon

    11. Mai 2011 um 16:14 Uhr


  • Was ist der Nachteil daran, diese öffentlichen Mitglieder zu machen? Müssen sie nach dem Bau repariert werden und stellen Sie keine anderen Mittel zur Verfügung, um die Werte zu ändern?

    – Mel

    11. Mai 2011 um 17:00 Uhr

  • Da Sie Ihre Frage nicht aktualisiert haben, um die angeforderten Informationen einzuschließen, verlinke ich Sie nur auf die Großer Klassencode-Geruch. Ich ermutige Sie, es zu lesen und die vorgeschlagenen Refactorings anzuwenden.

    – Gordon

    11. Mai 2011 um 17:24 Uhr


  • @Gordon – locker Kumpel, gib einem Typen Zeit! In Bezug auf das Large-Class-Syndrom haben Sie hier wahrscheinlich Recht – ich hoffe, dass meine Erklärungen im Anhang diese Bedenken ansprechen (wenn nicht rechtfertigen). Insbesondere erleichtert die Klassenextraktion oder Unterklassenextraktion nicht die Art von “Abkürzungs”-Instanziierung, die ich dem Benutzer der API zur Verfügung stellen möchte. Hoffe das macht Sinn. Danke für den Link, aber die tolle Seite hatte ich vergessen.

    – Tom Auger

    11. Mai 2011 um 17:54 Uhr

  • @Tom Danke für das Update. Die Klasse macht definitiv zu viel. Dies ist leicht an der Aufteilung der Eigenschaften in Form Behaviour und Form Paging zu erkennen. Obwohl es keine “Abkürzung” ist, würde ich dennoch versuchen, es in kleinere Teile umzugestalten, um die Komplexität der verschiedenen Komponenten herauszunehmen. Zum Instanziieren können Sie ein Factory- oder Builder-Muster verwenden.

    – Gordon

    11. Mai 2011 um 18:15 Uhr

Benutzer-Avatar
Daff

Ich kann mir zwei Möglichkeiten vorstellen, dies zu tun. Wenn Sie Ihre Instanzvariablen behalten möchten, können Sie einfach das an den Konstruktor übergebene Array durchlaufen und die Instanzvariable dynamisch festlegen:

    <?php

    class Foo {
        private $_type="default_type";
        private $_width = 100;
        private $_interactive = true;

        function __construct($args){
            foreach($args as $key => $val) {
                $name="_" . $key;
                if(isset($this->{$name})) {
                    $this->{$name} = $val;
                }
            }
        }
    }

    ?>

Wenn Sie den Array-Ansatz verwenden, müssen Sie nicht wirklich auf die Dokumentation verzichten. Verwenden Sie einfach die @property-Annotationen im Klassentext:

<?php

/**
 * @property string $type
 * @property integer $width
 * @property boolean $interactive
 */
class Foo {
    private $_instance_params = array(
        'type' => 'default_type',
        'width' => 100,
        'interactive' => true
    );

    function __construct($args){
        $this->_instance_params = array_merge_recursive($this->_instance_params, $args);
    }

    public function __get($name)
    {
        return $this->_instance_params[$name];
    }

    public function __set($name, $value)
    {
        $this->_instance_params[$name] = $value;
    }
}

?>

Allerdings wird eine Klasse mit 50 Member-Variablen entweder nur für die Konfiguration verwendet (die aufgeteilt werden kann) oder sie macht einfach zu viel und Sie sollten vielleicht über ein Refactoring nachdenken.

  • Ich mag beide Ihrer Ansätze und hatte nicht wirklich darüber nachgedacht, dass ich mit $this auf die Member-Variablen zugreifen würde und daher programmgesteuert darauf zugreifen könnte. Gibt isset() true zurück, wenn die Variable deklariert, aber kein Wert zugewiesen wurde (ich bin mir nicht einmal sicher, ob das in PHP Sinn macht). Ich denke nur – was ist, wenn ich keinen Standardwert für einen bestimmten Wert habe (zB: private $_foo;)?

    – Tom Auger

    11. Mai 2011 um 17:25 Uhr


  • Ich würde diesen null zuweisen. Auf diese Weise können Sie überprüfen, ob (isset ($this->{$name}) || $this->{$name} === null) …

    – Daff

    11. Mai 2011 um 17:41 Uhr

  • Nur um das klarzustellen, wenn ich Ihre obige Methode 1 implementieren würde, würde ich muss beim Deklarieren der Klasseneigenschaften einen Wert zuweisen? dh: private $foo = null; und vermeiden muss private $foo;? Ich dachte, isset() gab false zurück, wenn der Wert null wäre, und ich dachte, eine Member-Variable zu deklarieren, ohne ihr einen Wert zuzuweisen, der ihr null zuweist?

    – Tom Auger

    11. Mai 2011 um 19:45 Uhr

  • private $foo = null; und private $foo; ist genau das gleiche und isset gibt false zurück. Verwenden Eigenschaft_existiert wenn Sie überprüfen möchten, ob Sie die Eigenschaft in Ihrer Klasse überhaupt definiert haben.

    – Daff

    11. Mai 2011 um 20:31 Uhr


  • Danke für all die tollen Infos Daff

    – Tom Auger

    12. Mai 2011 um 14:34 Uhr

Ein anderer Ansatz besteht darin, die Klasse mit a zu instanziieren FooOptions Objekt, das nur als Optionscontainer fungiert:

<?php
class Foo 
{
    /*
     * @var FooOptions
     */
    private $_options;

    public function __construct(FooOptions $options) 
    {
        $this->_options = $options;
    }
}


class FooOptions
{
    private $_type="default_type";
    private $_width = 100;
    private $_interactive = true;

    public function setType($type);
    public function getType();

    public function setWidth($width);
    public function getWidth();

    // ...
}

Ihre Optionen sind gut dokumentiert und Sie haben eine einfache Möglichkeit, sie einzustellen/abzurufen. Dies erleichtert Ihnen sogar das Testen, da Sie verschiedene Optionsobjekte erstellen und festlegen können.

Ich erinnere mich nicht an den genauen Namen dieses Musters, aber ich denke, es ist Baumeister oder Möglichkeit Muster.

  • Das klingt eigentlich eher nach einem Modellmuster. Ich bin mir nicht sicher, ob Ihr Vorschlag tatsächlich gut funktioniert, es sei denn, Sie definieren eine Schnittstelle (kann PHP das tun?) IFooOptions und lassen dann den API-Benutzer diese Schnittstelle in einer MyOptions-Klasse implementieren oder diese Klasse erweitern (z. B.: MyFooOptions erweitert FooOptions) und übergeben eine Instanz davon an den Foo-Konstruktor. Dies könnte funktionieren, wenn der Benutzer Foo nur wenige Male instanziiert. In Situationen, in denen der Benutzer viele, viele Instanzen dieser Klasse erstellt und die Parameter möglicherweise dynamisch festgelegt werden müssen, wird dies sehr umständlich.

    – Tom Auger

    11. Mai 2011 um 17:48 Uhr

  • Ja, PHP kann Schnittstellen genauso wie Klassen definieren: interface IFooInterface. Sicherlich hat diese Methode ihre Schwächen und macht den Benutzern das Leben schwerer, aber Sie erhalten eine gut definierte API mit Ihren Klassenoptionen. Vielleicht ist dies für Ihren Fall nicht die beste Option … 🙂

    – Luis Damim

    11. Mai 2011 um 19:26 Uhr

Benutzer-Avatar
Tom Auger

Nur um weiterzuverfolgen, wie ich dies basierend auf einer der Lösungen von Daff implementiert habe:

    function __construct($args = array()){
        // build all args into their corresponding class properties
        foreach($args as $key => $val) {                
            // only accept keys that have explicitly been defined as class member variables
            if(property_exists($this, $key)) {
                $this->{$key} = $val;
            }
        }
    }

Verbesserungsvorschläge willkommen!

Benutzer-Avatar
Paolo_Mulder

Du könntest auch eine Elternklasse machen.

In dieser Klasse definieren Sie nur die Variablen.

protected function _SetVarName( $arg ){

   $this->varName=$arg;
}

Dann erweitern Sie diese Klasse in eine neue Datei und in dieser Datei erstellen Sie alle Ihre Prozesse.

Also bekommst du

classname.vars.php
classname.php

classname extends classnameVars {

}

Da die meisten standardmäßig eingestellt sind, müssen Sie nur diejenigen einstellen/zurücksetzen, die Sie benötigen.

$cn=new classname();
$cn->setVar($arg);    
//do your functions..

Ich verwende dies in einigen meiner Klassen. Erleichtert das Kopieren und Einfügen für eine schnelle Entwicklung.

private $CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType;
function __construct($CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType){
    $varsValues = array($CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType);
    $varNames = array('CCNumber', 'ExpMonth', 'ExpYear', 'CV3', 'CardType');
    $varCombined = array_combine($varNames, $varsValues);
    foreach ($varCombined as $varName => $varValue) {$this->$varName = $varValue;}
}

Schritte zu verwenden:

  1. Fügen Sie die Liste der Variablen aus Ihrer aktuellen __construct-Funktion ein und erhalten Sie sie, wobei Sie alle optionalen Parameterwerte entfernen
  2. Wenn Sie dies noch nicht getan haben, fügen Sie es ein, um Ihre Variablen für Ihre Klasse zu deklarieren, und verwenden Sie dabei den Bereich Ihrer Wahl
  3. Fügen Sie dieselbe Zeile in die Zeilen $varValues ​​und $varNames ein.
  4. Führen Sie eine Textersetzung auf “, $” für “‘, ‘” durch. Das wird alles außer dem ersten und letzten bekommen, die Sie manuell ändern müssen
  5. Genießen!

Benutzer-Avatar
Bytebereich

Nur eine kleine Verbesserung gegenüber Daffs erster Lösung zur Unterstützung von Objekteigenschaften, die möglicherweise einen Null-Standardwert haben und FALSE an die Bedingung isset() zurückgeben würden:

<?php

class Foo {
    private $_type="default_type";
    private $_width = 100;
    private $_interactive = true;
    private $_nullable_par = null;

    function __construct($args){
        foreach($args as $key => $val) {
            $name="_" . $key;
            if(property_exists(get_called_class(),$name))
                $this->{$name} = $val;
            }
        }
    }
}

?>

1157750cookie-checkPHP – beste Möglichkeit, ein Objekt mit einer großen Anzahl von Parametern und Standardwerten zu initialisieren

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy