Черты были одним из самых больших дополнений для PHP 5.4. Я знаю синтаксис и понимаю идею за чертами, например, повторное использование горизонтального кода для обычных вещей, таких как ведение журнала, безопасность, кэширование и т. Д.
Тем не менее, я до сих пор не знаю, как использовать черты в своих проектах.
Есть ли проекты с открытым исходным кодом, которые уже используют черты? Любые хорошие статьи / материалы для чтения о том, как структурировать архитектуры, используя черты?
Мое личное мнение заключается в том, что на самом деле очень мало применений для черт при написании чистого кода.
Вместо того, чтобы использовать черты для взлома кода в класс, лучше передавать зависимости через конструктор или через сеттеры:
class ClassName { protected $logger; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } // or public function setLogger(LoggerInterface $logger) { $this->logger = $logger; } }
Основная причина, по которой я считаю, что лучше, чем использование черт, заключается в том, что ваш код намного более гибкий, удаляя жесткую связь с признаком. Например, вы можете просто передать другой класс журнала. Это делает ваш код многоразовым и проверяемым.
Я думаю, что нужно было бы изучать языки, на которых есть Черты в течение некоторого времени, чтобы изучить принятые Хорошие / Лучшие практики. Мое настоящее мнение о Trait заключается в том, что вы должны использовать их только для кода, который вам придется дублировать в других классах, которые имеют одинаковую функциональность.
Пример для характеристики Logger:
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; }
Вышеупомянутое приведет к ошибке ( demo ). Аналогично, любые методы, объявленные в признаке, которые также уже объявлены в классе использования, не будут скопированы в класс, например
trait T { public function foo() { return 1; } } class A { use T; public function foo() { return 2; } } $a = new A; echo $a->foo();
напечатает 2 ( демо ). Это то, чего вы хотите избежать, потому что они затрудняют поиск ошибок. Вы также захотите избежать попадания вещей в черты, которые работают с свойствами или методами класса, который его использует, например
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();
работает ( demo ), но теперь черта тесно связана с A, и вся идея горизонтального повторного использования теряется.
Когда вы будете следовать принципу разделяемости интерфейса, у вас будет много небольших классов и интерфейсов. Это делает черты идеальным кандидатом на то, что вы упомянули, например, перекрестные проблемы , но не для создания объектов (в структурном смысле). В нашем примере Logger выше черта полностью изолирована. Он не имеет зависимости от конкретных классов.
Мы могли бы использовать агрегацию / состав (как показано в другом месте на этой странице) для достижения того же результирующего класса, но недостатком использования агрегации / композиции является то, что нам придется вручную добавлять методы прокси / делегирования для каждого класса, а затем быть в состоянии log. Черты решают это красиво, позволяя мне держать шаблон в одном месте и выборочно применять его там, где это необходимо.
Примечание. Учитывая, что черты являются новой концепцией в PHP, все высказанное выше мнение может быть изменено. У меня не было много времени, чтобы оценить концепцию. Но я надеюсь, что это достаточно хорошо, чтобы дать вам о чем подумать.
🙂 Мне не нравится теоретизировать и обсуждать, что нужно делать с чем-то. В этом случае черты. Я покажу вам, что я нахожу черты полезными, и вы можете либо научиться этому, либо проигнорировать его.
Черты – они отлично подходят для применения стратегий . Короче, шаблоны стратегии полезны, если вы хотите по-разному обрабатывать одни и те же данные (фильтровать, сортировать и т. Д.).
Например, у вас есть список продуктов, которые вы хотите отфильтровать, исходя из некоторых критериев (брендов, спецификаций и т. Д.) Или отсортированных с помощью различных средств (цена, ярлык, что угодно). Вы можете создать признак сортировки, который содержит различные функции для разных типов сортировки (числовые, строковые, даты и т. Д.). Затем вы можете использовать этот признак не только в своем классе продукта (как показано в примере), но и в других классах, которым нужны аналогичные стратегии (применять числовую сортировку к некоторым данным и т. Д.).
Попробуй:
<?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. Проблема возникает, когда расширения добавляют функциональность к базовому классу (например, пользовательскую модель), расширяя его. Это делается путем указания автозагрузчика Zend (через файл конфигурации XML), чтобы использовать модель User из расширения, и эта новая модель расширяет базовую модель. ( пример ). Но что, если два расширения переопределяют одну и ту же модель? Вы получаете «состояние гонки», и загружается только одна.
Решение прямо сейчас состоит в том, чтобы отредактировать расширения, чтобы один расширил класс переопределения модели другого в цепочке, а затем установил конфигурацию расширения, чтобы загрузить их в правильном порядке, чтобы цепь наследования работала.
Эта система часто вызывает ошибки, и при установке новых расширений необходимо проверить конфликты и редактировать расширения. Это боль и прерывает процесс обновления.
Я думаю, что использование Трейтов было бы хорошим способом выполнить одно и то же без этой раздражающей модели, переопределяющей «состояние гонки». Конечно, все еще могут быть конфликты, если несколько признаков используют методы с одинаковыми именами, но я бы предположил, что что-то вроде простого соглашения о пространстве имен может решить это по большей части.
TL; DR Я думаю, что черты могут быть полезны для создания расширений / модулей / плагинов для больших пакетов программного обеспечения PHP, таких как Magento.
У вас может быть черта для объекта только для чтения:
trait ReadOnly{ protected $readonly = false; public function setReadonly($value){ $this->readonly = (bool)$value; } public function getReadonly($value){ return $this->readonly; } }
Вы можете определить, используется ли эта черта и определить, что вы хотите или нет, вы должны написать этот объект в базе данных, файле и т. Д.