Черты на PHP - примеры реальных примеров/лучших практик?
Traits были одним из самых больших дополнений для PHP 5.4. Я знаю синтаксис и понимаю идею, лежащую в основе черт, например, повторное использование горизонтального кода для обычных вещей, таких как ведение журнала, безопасность, кэширование и т.д.
Однако я до сих пор не знаю, как использовать черты в своих проектах.
Есть ли проекты с открытым исходным кодом, которые уже используют черты? Любые хорошие статьи/материалы для чтения о том, как структурировать архитектуры, используя черты?
Ответы
Ответ 1
Мое личное мнение состоит в том, что при написании чистого кода на самом деле очень мало применений для черт.
Вместо того, чтобы использовать черты для взлома кода в класс, лучше передавать в зависимостях через конструктор или через сеттеры:
class ClassName {
protected $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
// or
public function setLogger(LoggerInterface $logger) {
$this->logger = $logger;
}
}
Основная причина, по которой я считаю, что лучше, чем использование признаков, заключается в том, что ваш код намного более гибкий, удаляя жесткую связь с признаком. Например, вы можете просто передать другой класс журнала. Это делает ваш код многоразовым и проверяемым.
Ответ 2
Я предполагаю, что нужно было бы изучить языки, на которых есть "Значения" в течение некоторого времени, чтобы изучить принятые Хорошие/Лучшие практики. Мое настоящее мнение о 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, и вся идея горизонтального повторного использования теряется.
Когда вы следуете принципу Interface Segregation Principle, у вас будет много небольших классов и интерфейсов. Это делает Traits идеальным кандидатом на то, что вы упомянули, например. перекрестные проблемы, но не для создания объектов (в структурном смысле). В нашем примере Logger выше черта полностью изолирована. Он не имеет зависимости от конкретных классов.
Мы могли бы использовать агрегацию/состав (как показано в другом месте на этой странице) для достижения того же результирующего класса, но недостаток использования агрегации/состав состоит в том, что нам придется вручную добавлять методы прокси/делегирования каждому классу, который должен иметь возможность регистрироваться. Черты решают это красиво, позволяя мне держать шаблон в одном месте и выборочно применять его там, где это необходимо.
Примечание. Учитывая, что черты являются новой концепцией в PHP, все высказанное выше мнение может быть изменено. У меня не было много времени, чтобы оценить концепцию. Но я надеюсь, что это достаточно хорошо, чтобы дать вам кое-что о чем подумать.
Ответ 3
:) Я не люблю теоретизировать и спорить о том, что нужно делать с чем-то. В этом случае черты. Я покажу вам, что я нахожу черты полезными, и вы можете учиться на нем или игнорировать его.
Черты - они подходят для применения стратегий . Короче говоря, шаблоны стратегий полезны, когда вы хотите по-разному обрабатывать одни и те же данные (фильтровать, сортировать и т.д.).
Например, у вас есть список продуктов, которые вы хотите отфильтровать, исходя из некоторых критериев (брендов, спецификаций и т.д.) или отсортированных с помощью различных средств (цена, ярлык, что угодно). Вы можете создать признак сортировки, который содержит различные функции для разных типов сортировки (числовые, строковые, даты и т.д.). Затем вы можете использовать этот признак не только в своем классе продукта (как указано в примере), но также и в других классах, которым нужны похожие стратегии (для применения числовой сортировки к некоторым данным и т.д.).
Попробуйте:
<?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>';
?>
В качестве заключительной заметки, я думаю о чертах, таких как аксессуары (которые я могу использовать для изменения моих данных). Подобные методы и свойства, которые могут быть вырезаны из моих классов и помещены в одно место, для упрощения обслуживания, более короткого и чистого кода.
Ответ 4
Меня волнует Traits, потому что они решают общую проблему при разработке расширений для платформы электронной коммерции Magento. Проблема возникает, когда расширения добавляют функциональность к базовому классу (например, пользовательскую модель), расширяя его. Это делается путем указания автозагрузчика Zend (через файл конфигурации XML), чтобы использовать модель User из расширения, и эта новая модель расширяет базовую модель. (пример). Но что, если два расширения переопределяют одну и ту же модель? Вы получаете "состояние гонки" и загружается только один.
Теперь решение состоит в том, чтобы отредактировать расширения, чтобы один расширил класс переопределения модели в цепочке и затем установил конфигурацию расширения, чтобы загрузить их в правильном порядке, чтобы цепь наследования работала.
Эта система часто вызывает ошибки, и при установке новых расширений необходимо проверить наличие конфликтов и изменить расширения. Это боль и прерывает процесс обновления.
Я думаю, что использование Traits было бы хорошим способом выполнить одно и то же без этой раздражающей модели, которая отменяет "состояние гонки". Конечно, все еще могут быть конфликты, если несколько признаков используют методы с одинаковыми именами, но я бы предположил, что что-то вроде простого соглашения о пространстве имен может решить это по большей части.
TL; DR Я думаю, что черты могут быть полезны для создания расширений/модулей/плагинов для больших пакетов программного обеспечения PHP, таких как Magento.
Ответ 5
У вас может быть черта для объекта только для чтения:
trait ReadOnly{
protected $readonly = false;
public function setReadonly($value){ $this->readonly = (bool)$value; }
public function getReadonly($value){ return $this->readonly; }
}
Вы можете определить, используется ли эта черта и определить, что вы хотите, или вы не должны писать этот объект в базе данных, файле и т.д.