web-dev-qa-db-ja.com

PHP-多数のパラメーターとデフォルト値を使用してオブジェクトを初期化するための最良の方法

私は、非常に複雑なオブジェクトを定義するクラスを設計しています。このクラスには、ほとんどオプションのパラメーターが1トン(50以上)あり、その多くにはデフォルトがあります(例:_$type = 'foo'; $width = '300'; $interactive = false;_)。次のことができるように、コンストラクターとインスタンス/クラス変数を設定するための最良の方法を決定しようとしています。

  • クラスを使いやすくする
  • クラスの自動ドキュメント化を簡単にします(つまり、phpDocumentorを使用します)
  • これをエレガントにコーディングする

上記に照らして、私はコンストラクターに大量の引数を渡したくありません。初期化値を含む単一のハッシュを渡します。例:$foo = new Foo(array('type'=>'bar', 'width'=>300, 'interactive'=>false));

クラスのコーディングに関しては、私はまだ私がむしろ持っているように感じています...

_class Foo {
    private $_type = 'default_type';
    private $_width = 100;
    private $_interactive = true;

    ...
}
_

...これによりドキュメントの生成が容易になると思います(クラスのプロパティのリストを取得して、APIユーザーが操作する必要のある「オプション」を知ることができます)。これは正しい方法のように「感じ」ます。それ。

しかし、コンストラクターの入力パラメーターをクラス変数にマッピングするという問題が発生し、シンボルテーブルを利用せずに、「ブルートフォース」アプローチに陥り、目的を打ち破ります(ただし、他の人にはオープンです)意見)。例えば。:

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

それ自体が連想配列である単一のクラス変数を作成することを検討しました。これを初期化するのは本当に簡単です。例:

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

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

しかし、これは私がプライベートクラス変数のようなネイティブ機能を利用していないようであり、ドキュメントの生成はこのアプローチでは機能しないように感じます。

ここまで読んでくれてありがとう。私はおそらくここで多くのことを求めていますが、私はPHPに不慣れで、これを行うための慣用的でエレガントな方法を本当に探しています。あなたのベストプラクティスは何ですか?


補遺(この特定のクラスに関する詳細)

このクラスはやりすぎである可能性が非常に高いですが、フォームを作成および処理するための古いPerlライブラリの移植版です。継承とポリモーフィズムを利用するために構成オプションを分割する方法はおそらくありますが、実際には逆効果になる可能性があります。

リクエストに応じて、ここにいくつかのパラメーター(Perlコード)の部分的なリストがあります。これらはサブクラスにうまくマッピングされていないことがわかります。

クラスには確かにこれらのプロパティの多くのゲッターとセッターがあるので、ユーザーはそれらをオーバーライドできます。この投稿の目的(および元のコードがうまく機能すること)は、必要なパラメーターが既に設定されているこれらのFormオブジェクトをインスタンス化するコンパクトな方法を提供することです。それは実際に非常に読みやすいコードになります。

_# 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"
_

もちろん、プログラミングスタイルに関するより長い議論に引き込まれることもできます。しかし、関係者全員の正気のために、私はそれを避けたいと思っています!ここ(Perlコード)は、かなり大量のパラメーターのセットを使用してこのオブジェクトをインスタンス化する例です。

_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
        );
_
30
Tom Auger

それを行うには2つの方法が考えられます。インスタンス変数を保持したい場合は、コンストラクターに渡された配列を反復処理して、インスタンス変数を動的に設定できます。

    <?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;
                }
            }
        }
    }

    ?>

アレイアプローチを使用する場合、ドキュメントを実際に放棄する必要はありません。クラス本体で@propertyアノテーションを使用するだけです。

<?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;
    }
}

?>

とは言うものの、50個のメンバー変数を持つクラスは、構成(分割可能)にのみ使用されるか、実行が多すぎるため、リファクタリングを検討することをお勧めします。

7
Daff

別のアプローチは、オプションコンテナとしてのみ機能するFooOptionsオブジェクトを使用してクラスをインスタンス化することです。

<?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();

    // ...
}

オプションは十分に文書化されており、それらを設定/取得する簡単な方法があります。これにより、さまざまなオプションオブジェクトを作成および設定できるため、テストも容易になります。

このパターンの正確な名前は覚えていませんが、BuilderまたはOptionパターンだと思います。

7
Luiz Damim

Daff's ソリューションの1つに基づいて、これをどのように実装したかをフォローアップするだけです。

    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;
            }
        }
    }

改善の提案を歓迎します!

2
Tom Auger

私はこれをいくつかのクラスで使用しています。コピーアンドペーストが簡単になり、迅速な開発が可能になります。

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;}
}

使用手順:

  1. 貼り付けて、現在の__construct関数から変数のリストを取得し、オプションのパラメーター値を削除します
  2. まだ行っていない場合は、選択したスコープを使用して、それを貼り付けてクラスの変数を宣言します
  3. 同じ行を$ varValues行と$ varNames行に貼り付けます。
  4. 「 '、'」の「、$」でテキスト置換を行います。手動で変更する必要がある最初と最後を除くすべてを取得します
  5. 楽しい!
0
Beachhouse

デフォルト値がnullで、isset()条件にFALSEを返すオブジェクトプロパティをサポートするためのDaffの最初のソリューションを少し改善しました。

<?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;
            }
        }
    }
}

?>
0
bytepan

親クラスを作成することもできます。

そのクラスでは、変数のみを定義します。

protected function _SetVarName( $arg ){

   $this->varName=$arg;
}

次に、そのクラスを新しいファイルに拡張し、そのファイルにすべてのプロセスを作成します。

だからあなたは得る

classname.vars.php
classname.php

classname extends classnameVars {

}

ほとんどがデフォルトであるため、必要なものを設定/リセットするだけで済みます。

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