PHP - 使用大量参数和默认值初始化对象的最佳方法

时间:2011-05-11 16:08:02

标签: php class constructor instantiation idioms

我正在设计一个类,它定义了一个高度复杂的对象,其中包含大量可选参数(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用户知道他们必须使用的'选项'),并且“感觉”就像是正确的这样做的方法。

但是你遇到了将构造函数中的传入参数映射到类变量的问题,并且在没有利用符号表的情况下,你进入了一种“暴力”方法,这让我无法实现目的(虽然我是对其他意见开放)。 E.g:

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代码)。您应该看到这些不能很好地映射到子类。

该类肯定拥有许多这些属性的getter和setter,因此用户可以覆盖它们;这篇文章的目标(以及原始代码做得很好)是提供一种使用已设置的必需参数实例化这些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
        );

6 个答案:

答案 0 :(得分:7)

我可以想到两种方法。如果你想保留你的实例变量,你可以迭代传递给构造函数的数组并动态设置实例变量:

    <?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个成员变量的类要么仅用于配置(可以拆分),要么只是做得太多而你可能想考虑重构它。

答案 1 :(得分:7)

另一种方法是使用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();

    // ...
}

您的选项已有详细记录,您可以轻松设置/检索它们。这甚至可以帮助您进行测试,因为您可以创建和设置不同的选项对象。

我不记得这个模式的确切名称,但我认为它是构建器选项模式。

答案 2 :(得分:2)

根据Daff's解决方案之一,跟进我的实施方式:

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

欢迎改进建议!

答案 3 :(得分:0)

你也可以创建一个父类。

在该课程中,您只定义变量。

protected function _SetVarName( $arg ){

   $this->varName=$arg;
}

然后将该类扩展到一个新文件中,并在该文件中创建所有进程。

所以你得到了

classname.vars.php
classname.php

classname extends classnameVars {

}

因为大部分都是默认的,所以你只需要设置/重置你需要的那些。

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

答案 4 :(得分:0)

我在我的一些课程中使用它。使复制和粘贴变得容易,以便快速开发。

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. 之外,所有这些都会得到
  6. 享受!

答案 5 :(得分:0)

对Daff的第一个支持可能具有空缺省值的对象属性的第一个解决方案稍微改进一下,并且会返回FALSE到isset()条件:

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

?>