2013-01-08 132 views
2

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

换句话说,有没有一种方法可以创建具有“可重写默认值”的特征并避免命名冲突?

回答

2

就像你会在每个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”,...:它们是通用的以便有用。

+1

,如果我错过了简单的方法,只是想知道(即使用命名约定或其他)来轻松配置/引入某些trait默认属性,并避免每次“手动”解决命名冲突。但似乎总是需要一个手动解决步骤的地方。 – Jails

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']; 
    }; 
} 
+0

谢谢,我想知道是否有一个更简单的方法,但看起来像你总是需要解决冲突并手工定义你的'__construct'(如果你不想使用'ReflectionClass')。 – Jails

+1

我不认为有另一种方法。我想这是你必须付出使用特质的一些样板代码。此外,它是一个最不明确的,而不是一些约定(读“魔术”);) –

1

有几种技术,其中multiple inheritance是灵丹妙药,但他们不是可行的PHP。

关于如何避免复杂的设计模式以保持简单的原则,当尝试执行诸如此类的操作时,会刺伤您后背。

是,Traits进行了介绍,这可能是一个强大的工具,但你可以看到它在的情况下,很多无用的,仅仅因为是properties没有conflict resolutionaliases

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

+0

你引用哪个选项?真正的面向方面的编程是不可能与PHP,或?我认为特质可以解决这个差距,但是当你命名它时,解决属性冲突是一个问题。 – velop

0

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

  1. 使用方法,而不是(它们可以被重写,并且存在冲突避免使用use trait {... as ...};其允许性状方法被重新定义为一些其它的名字(use trait {oldmethod as newmethod;}
  2. 使用性状定义集合/在所述性状得到逻辑重新定义孩子的属性,而不是儿童的私人属性
  3. 将整个行为更改为使用可调用钩子,并使用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))