4

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因为每个特征都需要自己的默认值。使用 trait 的名称作为属性名称意味着使用类似(new ReflectionClass(__CLASS__))->getTraits())... 这不是真的很棒。

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

4

4 回答 4

5

就像您在每个 OOP 概念中都会做的那样:睁开眼睛!这完全一样,因为您扩展了一个类并滥用了已经存在的属性。而已。

class A {
  protected $defaults = array ('foo' => 'bar');
}
class B extends A {
  protected $defaults = array('foo2' => 42); // Bum: 'foo' disappeared
}

无论如何,拥有一个通用的$_defaults-property 对我来说听起来像是一种代码味道:什么“默认值”?这是默认的?系统默认?应用程序默认值?[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] “默认”作为标识符就像“类型”、“状态”、“i”、...:它们是通用的以便有用。

于 2013-01-08T14:07:49.543 回答
3

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

当你试图做这样的事情时,关于保留复杂设计模式以保持简单的整个哲学都是在背后捅你一刀。

是的,Traits被介绍了,这可能是一个强大的工具,但正如你所看到的,它在很多情况下都是无用的,因为没有conflict resolutionaliasesfor properties

不过还有一个选择,看看Aspect-Oriented Programming(AOP)。很多人在掌握它时遇到问题,但我建议任何人深入研究它,这是一个在未来几年将变得非常重要的范例(即使对于 PHPers)

于 2013-06-06T14:21:07.873 回答
1

当他们需要从添加的类中获取信息时,您可以在您的 trait 中使用initor方法。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'];
    };
}
于 2013-01-08T14:13:33.220 回答
1

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

  1. 使用方法代替(它们可以被覆盖,并且使用它可以避免碰撞use trait {... as ...};,它允许将特征方法重新定义为其他名称(use trait {oldmethod as newmethod;}
  2. 在 trait 中使用 trait 定义的 set/get 逻辑来(重新)定义子级的属性,而不是子级私有
  3. 将整个行为更改为使用可调用的钩子,使用 null 钩子表示默认行为(这更有意义,考虑到我们决定默认应该是允许任何回调名称)

首先不要在 trait 中使用上面的代码,而是使用if ($this->isInAllowedCallbackNames),然后按照最初的意图在 trait 中定义属性private $fullTraitName_allowedCallbackName。在 中添加 in_array 逻辑trait->isInAllowedCallbackNames function,然后不覆盖 Child 中的属性,而是覆盖方法(可能使用您在 child 中定义的数组,如果需要调用重新定义的 parent/trait 方法来扩展行为)。

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

第三个选项是我选择的那个,因为我想要第一个选项,但我认为 PHP 的冲突避免方法会使代码更加混乱(在 delation 时更改函数名称是不好的)。因此,我创建trait->$methodFilter=null了一个函数trait->setMethodFilter($callable),它将可调用对象分配给 methodFilter(如果不可调用,则抛出异常),然后在我使用的特征中if ((is_callable($this->methodFilter)?$this->methodFilter($name):true))

于 2016-02-02T12:11:28.330 回答