Php 以编程方式构造具有未知数量参数(以及某些特定要求)的对象
我正在构建一个框架,我遇到了一个小问题。嗯,不仅仅是一个问题,我称之为挑战。在构建widget系统时,我发现我需要为我的所有widget提供一些基本功能,即Php 以编程方式构造具有未知数量参数(以及某些特定要求)的对象,php,oop,Php,Oop,我正在构建一个框架,我遇到了一个小问题。嗯,不仅仅是一个问题,我称之为挑战。在构建widget系统时,我发现我需要为我的所有widget提供一些基本功能,即build()静态方法,该方法允许我轻松实例化任何widget,然后链接下一个方法,如下所示: $code = MySpecificWidget::build()->getHTML(); 为此,我编写了以下代码: abstract class StandardWidget { public static function bui
build()
静态方法,该方法允许我轻松实例化任何widget,然后链接下一个方法,如下所示:
$code = MySpecificWidget::build()->getHTML();
为此,我编写了以下代码:
abstract class StandardWidget {
public static function build() {
$className = get_called_class(); // the "late static binding" classname
return new $className;
}
}
class MySpecificWidget extends StandardWidget {
public function getHTML() {
return '<b>Some HTML</b>';
}
}
因此,如果需要参数,只需在子类中编写\uu construct()
不幸的是,PHP解析器告诉我不能静态调用\uu construct()
因为我正在编写一个框架,所以我试图尽可能地轻量级。这就是挑战:
有没有办法做到这一点而不:
- 使用
eval()
- 使用反射
- 必须在每个子类中强制编写构造函数
build
方法位于所有小部件从中继承的抽象类中
我想最好的方法就是我上面写的方法,只要我能像那样调用神奇的构造函数…我不知道是否有任何特殊的需要为您构建方法,但您尝试的是一种工厂模式 解决问题的一个简单方法是说“小部件构造函数必须将数组作为单个参数”。
因此,您可以使用
func\u get\u args
获取参数,并将该数组传递给小部件构造函数。如何允许getHtml函数接受数组作为参数,然后开发人员可以根据需要使用该参数?您确定需要此静态构建方法吗?除了保存一个LOC,我看不出你的build()
方法比new
有什么好处
$widget = new MySpecificWidget($param1, $param2, $param500);
$code = $widget->getHTML();
应该可以很好地工作,并允许在构造函数中有不同的参数。我认为你的builded
方法引入了不必要的复杂性,并解决了一个非问题。因为\uuu-construct
初始化了一个对象,但没有分配对象,所以你不能使用new
或ReflectionClass::newInstance
(据我所知,这是分配新对象的唯一方法)。这意味着您可以使用new
创建一个实例,然后再次调用\u construct
来初始化对象
<?php
class A {
function __construct() {
echo __CLASS__ , '/', get_called_class(), "\n";
}
static function build() {
$class = get_called_class();
$args = func_get_args();
echo "Building $class:\n";
$instance = new static;
# don't bother calling constructor a second time if there are no arguments
if ($args) {
echo "Calling $class::__construct:\n";
call_user_func_array(array($instance, '__construct'), $args);
}
echo "done\n";
return $instance;
}
}
class B extends A {
function __construct($foo=Null, $bar=Null) {
if (! is_null($foo)) {
parent::__construct();
echo __CLASS__ , '/', get_called_class(), "::__construct($foo, $bar)\n";
}
}
}
class C extends A {}
$b = B::build(1, 2);
$c = C::build();
通过检查build
的参数计数,您可以在堆栈跟踪中进行更高级别的操作,以告知是否会有第二次调用,但这并不特别优雅
class HeavyWidget extends StandardWidget {
function __construct($id=Null) {
$trace = debug_backtrace();
if (! is_null($id) # there's an argument
|| count($trace) <= 2 # not called within `build`
|| $trace[1]['function'] == 'call_user_func_array' # direct
|| ($trace[2]['function'] == 'build' # indirect, no 2nd invocation
&& count($trace[2]['args']) == 0)
|| $trace[2]['function'] != 'build' # indirect, not called within `build`
)
{
/* direct invocation, or there aren't any arguments so there
* won't be a second (direct) invocation
*/
parent::__construct();
if (is_null($id)) {
$this->id = self::generateId();
} else {
$this->id = $id;
}
...
}
}
}
注意,这与前面的代码有一个类似的问题:如果第一个参数可能是(非参数)数组,而间接调用中的第二个为null,则可能无法根据参数模式区分直接调用和间接调用
class NopDelegate {
function __get($name) {}
function __call($name,$arguments) {}
function __callStatic($name,$arguments) {}
}
class ProblemWidget extends StandardWidget {
function __construct($values, $delegate=Null) {
parent::__construct();
$this->values = $values;
if (is_null($delegate)) {
$this->delegate = new NopDelegate;
}
}
}
$works = new ProblemWidget(array('a', 'b', 'c'));
$works2 = new ProblemWidget(array('a', 'b', 'c'), new SomeDelegate());
$oops = ProblemWidget::make(array('a', 'b', 'c'));
$oops2 = ProblemWidget::make(array('a', 'b', 'c'), new SomeDelegate());
$oops_also = ProblemWidget::make('a', 'b', 'c', new SomeDelegate());
因此,唯一真正透明的选择是使用反射并接受性能影响。反射的可能重复以及反射的错误是什么?构建方法可以允许进行其他操作,例如注册等。父构造函数不是更适合此操作的地方吗?@middus:事情是,它的整体思想是能够将构造函数和下面的方法链接在一行中。例如,如果我将小部件的HTML作为参数传递给另一个函数,或者将其放入数组中,则创建一个小部件的一个实例不会有太大问题。。。但假设我有5到10个小部件,那将是5到10行代码,这些代码只是为了把事情弄得一团糟。除非我以后想引用它们,否则保存它们是没有意义的。也许我应该在我的问题中更强调这一点。@Boro好的,明白了:这只是一个荣耀的sprintf
!?如果只是为了扔掉而实例化,那么为什么首先要实例化对象呢?只需将其包装在一个函数中,就完成了。或者,您可能需要引入某种接受参数的init
方法。不完全是这样,因为小部件中可能有一些复杂的功能。例如,寻呼机小部件必须解析它应该有多少页,然后才返回它的html。这不是一个坏主意,尽管有点麻烦,因为它不是透明的。在接受你的回答之前,我会等着看是否有人提出了其他的想法。谢谢是的,我想我最好还是反省一下。遗憾的是,它不能通过调用用户函数数组()实现。
。谢谢
class HeavyWidget extends StandardWidget {
function __construct($id=Null) {
$trace = debug_backtrace();
if (! is_null($id) # there's an argument
|| count($trace) <= 2 # not called within `build`
|| $trace[1]['function'] == 'call_user_func_array' # direct
|| ($trace[2]['function'] == 'build' # indirect, no 2nd invocation
&& count($trace[2]['args']) == 0)
|| $trace[2]['function'] != 'build' # indirect, not called within `build`
)
{
/* direct invocation, or there aren't any arguments so there
* won't be a second (direct) invocation
*/
parent::__construct();
if (is_null($id)) {
$this->id = self::generateId();
} else {
$this->id = $id;
}
...
}
}
}
class MySpecificWidget extends StandardWidget {
function __construct($args, $foo=Null) {
if (is_null($foo) && is_array($args)) {
/* called from StandardWidget::build(); $args contains all arguments.
* Extract them.
*/
$foo = $args[1];
$args = $args[0];
}
...
}
}
$frob = new MySpecificWidget(42,23);
class NopDelegate {
function __get($name) {}
function __call($name,$arguments) {}
function __callStatic($name,$arguments) {}
}
class ProblemWidget extends StandardWidget {
function __construct($values, $delegate=Null) {
parent::__construct();
$this->values = $values;
if (is_null($delegate)) {
$this->delegate = new NopDelegate;
}
}
}
$works = new ProblemWidget(array('a', 'b', 'c'));
$works2 = new ProblemWidget(array('a', 'b', 'c'), new SomeDelegate());
$oops = ProblemWidget::make(array('a', 'b', 'c'));
$oops2 = ProblemWidget::make(array('a', 'b', 'c'), new SomeDelegate());
$oops_also = ProblemWidget::make('a', 'b', 'c', new SomeDelegate());