具有属性的特征
PHP特征的目标是管理一堆逻辑。然而,根据一些专用属性并避免命名冲突,使这一堆逻辑能够工作的最好方法是什么?具有属性的特征
我在想MVC,特别是模型类。实际上,模型似乎是特征的良好候选者。模型可以实现树形结构,可绘制,可修改,可缩放等等。
我想编写这样的事:
class MyModel extends Model {
use Tree, Revision, Slug;
protected $_behaviors = array(
'Tree' => array('parentFieldname' => 'p_id'),
'Revision' => array(/* some options */),
'Slug' => array(/* some options */)
);
public function __construct() {
foreach($this->_behaviors as &$options) {
$options += /* trait defaults ? */
}
}
}
如果我打算设置树特征是这样的:
trait Tree {
protected $_defaults = array(
'parentFieldname' => 'parent_id',
/*...other options...*/
);
public function moveUp();
public function moveDown();
public function setParent(); //<- need the `'parentFieldname' => 'p_id'`attribute
/*...and so on...*/
}
我会深入到每个以来由于$_defaults
命名冲突特质需要它自己的默认值。使用特征的名称作为属性名称暗示使用类似(new ReflectionClass(__CLASS__))->getTraits())
......这不是很棒。
换句话说,有没有一种方法可以创建具有“可重写默认值”的特征并避免命名冲突?
就像你会在每个OOP概念中做的一样:睁开你的眼睛!这完全一样,因为你扩展了一个类并滥用了已有的属性。而已。
class A {
protected $defaults = array ('foo' => 'bar');
}
class B extends A {
protected $defaults = array('foo2' => 42); // Bum: 'foo' disappeared
}
有一个通用的$_defaults
- 属性的声音,我像一个代码味道反正什么“默认”?这个默认值?系统默认?应用程序默认? [1]设置你的使用值,而不是“默认”级,因为多数民众赞成(扩展默认值)的东西用于初始化过程,或具体属性(public $parentFieldName = 'parent_id';
)
trait A {
public $parentFieldName = 'parent_id';
public function construct ($options) {/
if (isset($options['parentFieldName'])) {
$this->parentFieldName = $options['parentFieldName'];
}
}
}
class Foo {
use A {
A::construct as protected constructA;
}
public function __construct ($options) {
$this->constructA($options['A']);
}
}
的一些注意事项的初始化:这是重要,你别名construct()
,因为它会与其他方法(来自其他特性)冲突,construct()
不是一个特殊的方法。它只是以这种方式命名(从我这里)澄清,它是“一种”的构造函数。其他名称,如init()
等等当然也会起作用;) 您必须自己在“真实”构造函数中调用它(请参阅Foo::__construct()
)。
[1]作为标识符的“default”就像是“type”,“status”,“i”,...:它们是通用的以便有用。
当你需要从他们添加的类中获取信息时,你可以在你的特质中有一个init
或setOptions
方法。然后您可以从您的__construct
方法调用此方法。
真正的问题是,你不能在特征属性和类属性之间产生冲突,这会导致致命错误。没有冲突解决方法,就像在方法名称中一样。
最简单的事情将是有这样的事情:
trait Tree {
protected $Tree_options; // Prefixing with the trait name should protect you from naming collision (less than optimal)
public function init($options) {
// This should be called int he __construct of MyModel
$this->Tree_options = array_merge($this->Tree_options, $options);
}
public function moveUp();
public function moveDown();
public function setParent() {
$parent = $this->Tree_options['p_id'];
};
}
谢谢,我想知道是否有一个更简单的方法,但看起来像你总是需要解决冲突并手工定义你的'__construct'(如果你不想使用'ReflectionClass')。 – Jails
我不认为有另一种方法。我想这是你必须付出使用特质的一些样板代码。此外,它是一个最不明确的,而不是一些约定(读“魔术”);) –
有几种技术,其中multiple inheritance
是灵丹妙药,但他们不是可行的PHP。
关于如何避免复杂的设计模式以保持简单的原则,当尝试执行诸如此类的操作时,会刺伤您后背。
是,Traits
进行了介绍,这可能是一个强大的工具,但你可以看到它在的情况下,很多无用的,仅仅因为是properties
没有conflict resolution
或aliases
。
还有一个选项,看看Aspect-Oriented Programming(AOP)
。很多人都遇到了问题,但我会建议任何人进入它,这是一个范例,这将变得非常重要(即使对于PHPers)未来几年
你引用哪个选项?真正的面向方面的编程是不可能与PHP,或?我认为特质可以解决这个差距,但是当你命名它时,解决属性冲突是一个问题。 – velop
我有一个范例类似的问题在我的实施。我必须扩展Controller类来创建我的Child,但它也需要是Dynamic的特性,因为并非所有的Dynamics都是控制器,我不想使用相同的代码编写ControllerDynamic和nonControllerDynamic在里面。
我碰到了一个数组:trait->$allowedCallbackNames
在性状中定义的问题,由性状使用为if(in_array(name, $this->allowedCallbackNames))
,然后在子中不能被覆盖。你有同样的问题。我们击中了3个选项。
- 使用方法,而不是(它们可以被重写,并且存在冲突避免使用
use trait {... as ...};
其允许性状方法被重新定义为一些其它的名字(use trait {oldmethod as newmethod;}
) - 使用性状定义集合/在所述性状得到逻辑重新定义孩子的属性,而不是儿童的私人属性
- 将整个行为更改为使用可调用钩子,并使用null钩子表示默认行为(考虑到我们已经决定,这更有意义默认应允许任何回调名称)
首先,不是在性状中使用上面的代码,而是使用if ($this->isInAllowedCallbackNames)
,然后按照最初的预期定义性状中的属性private $fullTraitName_allowedCallbackName
。然后在trait->isInAllowedCallbackNames function
中添加in_array逻辑,而不是覆盖Child中的属性,重写该方法(假定使用您在子中定义的数组,并且需要调用重新定义的父/特征方法来扩展行为)。
第二个选项只需添加public function getAllowedCallbackNames()
和public function setAllowedCallbackNames()
的特质,然后叫他们init
功能设置默认为array_merge(getAllowedCallbackNames(), array(<new names>))
三opion是一个我已经用了,因为我想第一个选项,但我认为PHPs碰撞避免方法会导致messier代码(在解除时更改函数名称是不好的)。所以我创建trait->$methodFilter=null
和分配调用到methodFilter(并抛出一个异常,如果它不是可调用的)的功能trait->setMethodFilter($callable)
,然后在他们的特性我用if ((is_callable($this->methodFilter)?$this->methodFilter($name):true))
,如果我错过了简单的方法,只是想知道(即使用命名约定或其他)来轻松配置/引入某些trait默认属性,并避免每次“手动”解决命名冲突。但似乎总是需要一个手动解决步骤的地方。 – Jails