Подтвердить что ты не робот

PHP - лучший способ инициализировать объект с большим количеством параметров и значений по умолчанию

Я разрабатываю класс, который определяет очень сложный объект с тонны (50+) в основном необязательных параметров, многие из которых будут иметь значения по умолчанию (например: $type = 'foo'; $width = '300'; $interactive = false;). Я пытаюсь определить лучший способ настроить конструктор и переменные экземпляра/класса, чтобы иметь возможность:

  • упростить использование класса
  • упростить автоматическую документацию класса (т.е. используя phpDocumentor)
  • code this elegantly

В свете вышеизложенного я не хочу передавать конструктору массу аргументов. Я передам ему один хэш, содержащий значения инициализации, например: $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 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 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 "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
        );
4b9b3361

Ответ 1

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

protected function _SetVarName( $arg ){

   $this->varName=$arg;
}

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

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

classname.vars.php
classname.php

classname extends classnameVars {

}

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

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

Ответ 5

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

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

Шаги для использования:

  • Вставить и получить список переменных из текущей функции __construct, удалив любые дополнительные значения параметров
  • Если вы еще этого не сделали, вставьте это объявление, чтобы объявить переменные для своего класса, используя область вашего выбора.
  • Вставьте эту же строку в строки $varValues ​​и $varNames.
  • Текст заменяется на ", $" для "','". Это принесет все, кроме первого и последнего, что вам придется вручную изменить
  • Наслаждайтесь!

Ответ 6

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

?>