PHP - 使用大量参数和默认值初始化对象的最佳方法
我正在设计一个类,它定义一个高度复杂的对象,其中包含大量(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 代码)。您应该看到这些不能很好地映射到子类。
该类当然具有许多此类属性的 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
);
I'm designing a class that defines a highly complex object with a ton (50+) of mostly optional parameters, many of which would have defaults (eg: $type = 'foo'; $width = '300'; $interactive = false;
). I'm trying to determine the best way to set up the constructor and instance/class variables in order to be able to:
- make it easy to use the class
- make it easy to auto-document the class (ie: using phpDocumentor)
- code this elegantly
In light of the above, I don't want to be passing the constructor a ton of arguments. I will be passing it a single hash which contains the initialization values, eg: $foo = new Foo(array('type'=>'bar', 'width'=>300, 'interactive'=>false));
In terms of coding the class, I still feel like I would rather have...
class Foo {
private $_type = 'default_type';
private $_width = 100;
private $_interactive = true;
...
}
...because I believe this would facilitate documentation generation (you get the list of the class' properties, which lets the API user know what 'options' they have to work with), and it "feels" like the right way to do it.
But then you run into the problem of mapping the incoming parameters in the constructor to the class variables, and without exploiting the symbol table, you get into a "brute force" approach which to me defeats the purpose (though I'm open to other opinions). E.g.:
function __construct($args){
if(isset($args['type'])) $_type = $args['type']; // yuck!
}
I've considered creating a single class variable that is itself an associative array. Initializing this would be really easy then, e.g.:
private $_instance_params = array(
'type' => 'default_type',
'width' => 100,
'interactive' => true
);
function __construct($args){
foreach($args as $key=>$value){
$_instance_params[$key] = $value;
}
}
But this seems like I'm not taking advantage of native features like private class variables, and it feels like documentation generation will not work with this approach.
Thanks for reading this far; I'm probably asking a lot here, but I'm new to PHP and am really just looking for the idiomatic / elegant way of doing this. What are your best practices?
Addendum (details about this particular Class)
It's quite likely that this class is trying to do too much, but it is a port of an old Perl library for creating and processing forms. There's probably a way of dividing the configuration options to take advantage of inheritance and polymorphism, but it may actually be counter-productive.
By request, here is a partial listing of some of the parameters (Perl code). You should see that these don't map very well to sub-classes.
The class certainly has getters and setters for many of these properties so the user can over-ride them; the objective of this post (and something the original code does nicely) is to provide a compact way of instantiating these Form objects with the required parameters already set. It actually makes for very readable 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"
We can, of course, allow ourselves to be drawn into a more lengthy debate around programming style. But I'm hoping to avoid it, for the sanity of all involved! Here (Perl code, again) is an example of instantiating this object with a pretty hefty set of parameters.
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
);
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
我可以想到两种方法来做到这一点。如果您想保留实例变量,您只需迭代传递给构造函数的数组并动态设置实例变量:
使用数组方法时,您实际上不必放弃文档。只需在类主体中使用 @property 注释即可:
也就是说,具有 50 个成员变量的类要么仅用于配置(可以拆分),要么它做得太多,您可能需要考虑重构它。
I can think of two ways of doing that. If you want to keep your instance variables you can just iterate through the array passed to the constructor and set the instance variable dynamically:
When using the array approach you don't really have to abandon documentation. Just use the @property annotations in the class body:
That said, a class with 50 member variables is either only used for configuration (which can be split up) or it is just doing too much and you might want to think about refactoring it.
另一种方法是使用 FooOptions 对象实例化该类,仅充当选项容器:
您的选项有详细记录,并且您有一种简单的方法来设置/检索它们。这甚至可以方便您的测试,因为您可以创建和设置不同的选项对象。
我不记得这个模式的确切名称,但我认为它是 Builder 或 Option 模式。
Another approach is to instantiate the class with a
FooOptions
object, acting solely as an options container:Your options are well documented and you have an easy way to set/retrieve them. This even facilitates your testing, as you can create and set different options objects.
I don't remember the exact name of this pattern, but I think it's Builder or Option pattern.
只是为了跟进我如何实现这一点,基于 Daff 解决方案之一:
欢迎改进建议!
Just to follow up with how I implemented this, based on one of Daff's solutions:
Improvement suggestions welcomed!
您还可以开设家长班。
在该类中您只需定义变量。
然后将该类扩展到一个新文件中,并在该文件中创建所有进程。
所以你会得到
因为大多数都是默认的,所以你只需要设置/重置你需要的。
You also could make a parent class.
In that class you only define the variables.
Then extend that class into a new file and in that file you create all your processes.
So you get
Because most will be on default you only have to Set/Reset the ones you need.
我在一些课程中使用了这个。可以轻松复制和粘贴以实现快速开发。
使用步骤:
I use this on a few of my classes. Makes it easy to copy and paste for rapid development.
Steps to use:
对 Daff 的第一个解决方案进行一点改进,以支持可能具有 null 默认值的对象属性,并且会向 isset() 条件返回 FALSE:
Just a little improvement on Daff's first solution to support object properties that may have a null default value and would return FALSE to the isset() condition: