PHP中的特征 – 任何真实世界的例子/最佳实践?

性状已经成为PHP 5.4中最大的补充之一。 我知道语法和理解特质背后的想法,比如像日志logging,安全性,caching等常见的东西的横向代码重用。

然而,我仍然不知道如何在我的项目中利用特质。

有没有使用特质的开源项目? 任何有关如何使用特质来构build架构的好文章/阅读材料?

我个人的观点是,在编写干净的代码时,实际上对于特性的应用是非常less的。

代替使用特征将代码破解到类中,最好通过构造函数或setter来传递依赖关系:

class ClassName { protected $logger; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } // or public function setLogger(LoggerInterface $logger) { $this->logger = $logger; } } 

我发现比使用特征更好的主要原因是,通过消除与特征的硬耦合,你的代码更加灵活。 例如,您现在可以简单地传递一个不同的logging器类。 这使您的代码可重用和可testing。

我想一个人将不得不研究有特质的语言一段时间来学习被接受的良好/最佳实践。 我目前对Trait的看法是,你应该只使用它们代码,你将不得不在其他共享相同function的类中复制。

logging器特征示例:

 interface Logger { public function log($message, $level); } class DemoLogger implements Logger { public function log($message, $level) { echo "Logged message: $message with level $level", PHP_EOL; } } trait Loggable // implements Logger { protected $logger; public function setLogger(Logger $logger) { $this->logger = $logger; } public function log($message, $level) { $this->logger->log($message, $level); } } class Foo implements Logger { use Loggable; } 

然后你做( 演示 )

 $foo = new Foo; $foo->setLogger(new DemoLogger); $foo->log('It works', 1); 

我想在使用特质时要考虑的重要一点是,它们实际上只是复制到类中的代码片段。 这很容易导致冲突,例如,当您尝试更改方法的可见性时,例如

 trait T { protected function foo() {} } class A { public function foo() {} } class B extends A { use T; } 

以上将导致一个错误( 演示 )。 同样,在trait中声明的任何已经在using类中声明的方法都不会被复制到类中,例如

 trait T { public function foo() { return 1; } } class A { use T; public function foo() { return 2; } } $a = new A; echo $a->foo(); 

将打印2( 演示 )。 这些是你想避免的,因为他们很难find错误。 你也将要避免把东西放入对使用它的类的属性或方法进行操作的特性,例如

 class A { use T; protected $prop = 1; protected function getProp() { return $this->prop; } } trait T { public function foo() { return $this->getProp(); } } $a = new A; echo $a->foo(); 

作品( 演示 ),但现在这个特质与A紧密相连,整个横向重用的想法就失去了。

当你遵循接口隔离原则时,你将会有很多小的类和接口。 这使Traits成为你提到的事物的理想候选者,例如横切关注点 ,而不是构造对象(从结构意义上说)。 在我们上面的Logger例子中,特质是完全孤立的。 它不依赖于具体的类。

我们可以使用聚合/合成 (如本页其他地方所示)来实现相同的结果类,但是使用聚合/合成的缺点是我们必须手动将代理/委托方法添加到每个类,然后应该能够login。 性状很好地解决了这个问题,允许我将样板放在一个地方,并在需要时有select地应用。

注意:鉴于PHP中的特性是一个新的概念,上面expression的所有观点都可能会发生变化。 我还没有太多时间来评估这个概念。 但是我希望能够给你一些想法。

:)我不喜欢理论和辩论什么应该做的东西。 在这种情况下,性状。 我会告诉你我发现的有用的特征,你可以从中学习,或者忽略它。

特质 – 他们非常适合应用策略 。 简而言之,策略devise模式在您希望以不同方式处理(过滤,sorting等)相同的数据时非常有用。

例如,您可以根据某些条件(品牌,规格等)筛选出想要过滤的产品列表,或者使用不同的方法(价格,标签等)对其进行sorting。 您可以创build一个sorting特征,包含不同的sortingtypes(数字,string,date等)的不同function。 然后,不仅可以在产品类(如示例中给出的)中使用此特征,还可以在需要类似策略的其他类中使用此特征(对某些数据应用数字sorting等)。

尝试一下:

 <?php trait SortStrategy { private $sort_field = null; private function string_asc($item1, $item2) { return strnatcmp($item1[$this->sort_field], $item2[$this->sort_field]); } private function string_desc($item1, $item2) { return strnatcmp($item2[$this->sort_field], $item1[$this->sort_field]); } private function num_asc($item1, $item2) { if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0; return ($item1[$this->sort_field] < $item2[$this->sort_field] ? -1 : 1 ); } private function num_desc($item1, $item2) { if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0; return ($item1[$this->sort_field] > $item2[$this->sort_field] ? -1 : 1 ); } private function date_asc($item1, $item2) { $date1 = intval(str_replace('-', '', $item1[$this->sort_field])); $date2 = intval(str_replace('-', '', $item2[$this->sort_field])); if ($date1 == $date2) return 0; return ($date1 < $date2 ? -1 : 1 ); } private function date_desc($item1, $item2) { $date1 = intval(str_replace('-', '', $item1[$this->sort_field])); $date2 = intval(str_replace('-', '', $item2[$this->sort_field])); if ($date1 == $date2) return 0; return ($date1 > $date2 ? -1 : 1 ); } } class Product { public $data = array(); use SortStrategy; public function get() { // do something to get the data, for this ex. I just included an array $this->data = array( 101222 => array('label' => 'Awesome product', 'price' => 10.50, 'date_added' => '2012-02-01'), 101232 => array('label' => 'Not so awesome product', 'price' => 5.20, 'date_added' => '2012-03-20'), 101241 => array('label' => 'Pretty neat product', 'price' => 9.65, 'date_added' => '2012-04-15'), 101256 => array('label' => 'Freakishly cool product', 'price' => 12.55, 'date_added' => '2012-01-11'), 101219 => array('label' => 'Meh product', 'price' => 3.69, 'date_added' => '2012-06-11'), ); } public function sort_by($by = 'price', $type = 'asc') { if (!preg_match('/^(asc|desc)$/', $type)) $type = 'asc'; switch ($by) { case 'name': $this->sort_field = 'label'; uasort($this->data, array('Product', 'string_'.$type)); break; case 'date': $this->sort_field = 'date_added'; uasort($this->data, array('Product', 'date_'.$type)); break; default: $this->sort_field = 'price'; uasort($this->data, array('Product', 'num_'.$type)); } } } $product = new Product(); $product->get(); $product->sort_by('name'); echo '<pre>'.print_r($product->data, true).'</pre>'; ?> 

作为结束语,我想到了诸如配件之类的特征(我可以用它来改变我的数据)。 类似的方法和属性,可以从我的课堂切断,并放在一个地方,易于维护,更短,更干净的代码。

我对Traits感到兴奋,因为他们在为Magento电子商务平台开发扩展时解决了一个共同的问题 。 当扩展添加function到一个核心类(如说用户模型)通过扩展它会发生问题。 这是通过指向Zend自动加载器(通过XMLconfiguration文件)来使用扩展中的用户模型,并使新模型扩展了核心模型。 ( 例子 )但是如果两个扩展覆盖相同的模型呢? 你得到一个“竞赛条件”,只有一个被加载。

现在的解决scheme是编辑扩展,以便在链中扩展另一个模型重写类,然后设置扩展configuration以正确的顺序装载它们,以便inheritance链起作用。

这个系统经常导致错误,当安装新的扩展时,有必要检查冲突和编辑扩展。 这是一个痛苦,并打破升级过程。

我认为使用特质将是一个很好的方式来完成同样的事情,而这种烦人的模型重写“竞争条件”。 当然,如果多个traits实现相同名称的方法,仍然可能会有冲突,但我想象一个简单的命名空间约定可以解决这个问题。

TL; DR我认为Traits可以用来为像Magento这样的大型PHP软件包创build扩展/模块/插件。

你可以有一个像这样的只读对象的特质:

  trait ReadOnly{ protected $readonly = false; public function setReadonly($value){ $this->readonly = (bool)$value; } public function getReadonly($value){ return $this->readonly; } } 

你可以检测到这个特征是否被使用,并确定你是否应该在数据库,文件等中写入这个对象。