具有自己属性的特征

时间:2013-01-08 14:05:19

标签: php traits

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 ? */
        }
    }
}

如果我打算像这样设置Tree特征:

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())的东西......这真的不是很棒。

换句话说,有没有办法用“可覆盖的默认值”创建特征并避免命名冲突?

4 个答案:

答案 0 :(得分:3)

就像你在每个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]“默认”作为标识符就像“类型”,“状态”,“我”,......:它们是通用的有用的。

答案 1 :(得分:2)

有几种技术multiple inheritance是银弹,但它们在PHP中无法实现。

在尝试做这些事情时,关于保留复杂设计模式以保持简单的整体理念是刺伤你。

是的,引入了Traits,这可能是一个强大的工具,但正如您所看到的那样,在很多情况下它只是因为{{conflict resolutionaliases没有用properties 1}}。

还有一个选项,请看Aspect-Oriented Programming(AOP)。很多人在抓住它时遇到了问题,但是我建议任何人都可以深入研究它,这是一个范例,即将在未来几年变得非常重要(即使对于PHPers来说)

答案 2 :(得分:0)

当您需要从添加的类中获取信息时,您可以在特征中使用initsetOptions方法。然后,您可以使用__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'];
    };
}

答案 3 :(得分:0)

我的实施中遇到了类似的问题。我必须扩展Controller类,以创建我的Child,但它也需要具有特性Dynamic,因为并非所有Dynamics都是控制器而且我不想用ControllerDynamic和nonControllerDynamic编写代码。相同的代码。
我遇到了一个问题:在特征中定义的数组trait->$allowedCallbackNames,由特征使用if(in_array(name, $this->allowedCallbackNames)),然后不能在Child中重写。你有同样的问题。我们遇到了3个选项。

  1. 使用方法(可以覆盖它们,并且使用use trait {... as ...};避免碰撞,这允许将特征方法重新定义为其他名称(use trait {oldmethod as newmethod;}
  2. 在特征中使用trait定义的set / get逻辑来(重新)定义子元素的属性,而不是将其作为子元素的私有属性
  3. 更改整个行为以使用可调用挂钩,使用空挂钩意味着默认行为(这更有意义,考虑到我们决定默认应该允许任何callbackNames)
  4. 首先,不要使用特征中的上述代码,而是使用if ($this->isInAllowedCallbackNames),然后按照最初的预期在特征中定义属性private $fullTraitName_allowedCallbackName。在trait->isInAllowedCallbackNames function中添加in_array逻辑,然后重写该方法(可能使用您在子节点中定义的数组,并且如果需要调用重新定义的父/特征方法来扩展行为)。

    第二个选项只需将public function getAllowedCallbackNames()public function setAllowedCallbackNames()添加到特征中,然后在init函数中调用它们以将默认值设置为array_merge(getAllowedCallbackNames(), array(<new names>))

    第三个opion是我已经用过的那个,因为我想要第一个选项,但我认为PHPs避免碰撞的方法会使代码变得更加混乱(在接收时更改函数名称是不好的)。所以我创建了trait->$methodFilter=null和一个函数trait->setMethodFilter($callable),它将callable分配给methodFilter(如果它不可调用则抛出异常),然后在我使用{{{ 1}}