PHP - лучший способ инициализировать объект с большим количеством параметров и значений по умолчанию - PullRequest
30 голосов
/ 11 мая 2011

Я разрабатываю класс, который определяет очень сложный объект с массой (более 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
        );

Ответы [ 6 ]

7 голосов
/ 11 мая 2011

Другой подход заключается в создании экземпляра класса с объектом 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 или Опция шаблон.

7 голосов
/ 11 мая 2011

Я могу придумать два способа сделать это.Если вы хотите сохранить переменные вашего экземпляра, вы можете просто перебрать массив, переданный в конструктор, и динамически установить переменную экземпляра:

    <?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 переменными-членами либо используется только для конфигурации (которая может быть разделена), либо он делает слишком много иВы можете подумать о рефакторинге.

2 голосов
/ 12 мая 2011

Просто чтобы узнать, как я это реализовал, на основе одного из решений Даффа :

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

Предложения по улучшению приветствуются!

0 голосов
/ 06 января 2016

Просто небольшое улучшение первого решения 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;
            }
        }
    }
}

?>
0 голосов
/ 21 декабря 2012

Я использую это на нескольких моих уроках.Упрощает копирование и вставку для быстрой разработки.

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 голосов
/ 11 мая 2011

Вы также можете создать родительский класс.

В этом классе вы определяете только переменные.

protected function _SetVarName( $arg ){

   $this->varName=$arg;
}

Затем расширьте этот класс в новый файл, и в этом файле вы создадите все свои процессы.

Итак, вы получите

classname.vars.php
classname.php

classname extends classnameVars {

}

Поскольку большинство будет по умолчанию, вам нужно только установить / сбросить те, которые вам нужны.

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