Диагноз: Трейтизм

от автора

Всем привет.

Сегодня я хочу рассказать о довольно забавном способе использования РНР и трейтов — сборке класса с нужным вам функционалом, по кусочкам.

Интересно? Тогда прошу под кат.

Прежде, чем мы перейдём непосредственно к делу, давайте совершим небольшую прогулку в РНР.

Как это было

Итак, в своё время, при разработке РНР 5.0 были введены классы, что стало самым значительным отличием пятой версии от четвёртой.
Классы привнесли свежий ветер в язык, сделав его ещё привлекательнее для программистов, заинтересованных в ООП.

Я застал этот переход и ещё больше вдохновился этим в чём-то парадоксальным языком.
Со временем я стал повсеместно замечать довольно странное использование классов — фанатическое внедрение ООП там, где оно, в принципе, не нужно.
Но я отвлёкся, вернёмся ближе к теме. Одна из проблем классов заключалась в том, что они практически не помогали решить вопрос повторного использования. Особенно это стало заметно среди -быдло-среднестатистического кода. Наследование, на мой взгляд, только усугубляло ситуацию.
В результате стали появляться классы-потомки, наделённые кучей методов, которые не используются. В добавок сильная связанность классов — стала нормой, которая маскировалась возможностью расширения и использованием своих «связок».

Чтобы как-то повлиять на сложившуюся сиутацию, в версии 5.4 были введены трейты (traits), также известные как примеси, миксины.
Суть достаточно проста: наконец-то появились концептуально корректные варианты добавить классу необходимую функциональность, не связывая его с другими классами.

Первые размышления

Наблюдая всю эту картину, я периодически задумывался о том, что класс по своей сути — всего лишь структура, оболочка для нескольких связанных логикой функций и свойств. И настал таки момент, когда я решился на пробу новой концепции.

Раз класс, по моему виденью, просто оболочка, то почему бы не представить его как мир, наделённый свойствами и действиями, которые могут быть связаны, а могут и нет; могут знать о существовании друг друга, а могут и не знать.

Результатом стала сборка класса из трейтов. Т.к. в трейтах позволяется определять и магические методы, то результирующий класс может быть ничем не обделён, по сравнению с «обычными» классами. А удобный синтаксис подключения трейтов, позволяет элементарно автоматизировать сборку.

Небольшой пример

Давайте я приведу небольшой фрагмент кода с подключением трейтов, чтобы было понятнее о чём я говорю:

<?php  require_once 'traits/Rivers.php'; require_once 'traits/Rocks.php'; require_once 'traits/Lands.php'; require_once 'traits/Sky.php'; require_once 'traits/Animals.php'; require_once 'traits/Birds.php'; require_once 'traits/PHP.php';  class World { 	use Rivers, Rocks, Lands, Sky, Animals, Birds, PHP; } 

Я специально привёл require, чтобы было видно, что трейты реквайрятся также, как классы. Ещё для наглядности приведу пример пары трейтов:

traits/Rivers.php:

<?php  trait Rivers { 	public $rivers = [];  	public function getRubyFromTheBottom(){...} }  

traits/Animals.php:

<?php  trait Animals { 	public $animals = [];  	public function drinkWater() 	{ 		if(!empty($this->rivers)) 			$water = array_shift($this->rivers); 	} } 

Примеры лишены особого смысла, они просто показывают, что ничто не мешает взаимодействовать трейтам между собой.

Что я хочу

Итак, что я хочу — один класс, в котором есть всё необходимое без надобности подгружать ещё что-либо. Кроме того, я должен иметь полный контроль за тем, что подключается.

Последнее условие решается тривиальным конфигом сборки в духе

<?php  return ["Rivers", "Rocks", "Lands", "Sky", "Animals", "Birds", "PHP"]; 

В примере я испоьзую нативные массивы, чтобы не привлекать лишние функции по парсингу конфигов. Да нам и неважно сейчас, в каком виде хранятся настройки сборки.

Я не буду приводить код сборщика — если кому-то интересно, пишите в личку: скину ссылку на проект сделанный в такой концепции.

Давайте пройдёмся по очевидным плюсам/минусам данного подхода, а потом я поделюсь немножко практическим опытом.

Минусы:

  • Нет поддержки в IDE (пользуюсь штормом). Я имею в виду, что пока я не нашёл возможности подсказать шторму, что в данном трейте я использую метод из такого-то трейта (но машинально проставляю в докблоках use). Если кто-то знает, как решить эту проблему — буду рад комментариям (и добавлю в статью).
  • Уничтожается смысл private и protected полей, т.к. класс у нас один. Минус, лично для меня, сомнительный, но всё же посчитал нужным его указать.
  • Непривычное управление сборкой проекта. Да, по началу действительно странно, что настройка проекта сводится, по сути, к настройке сборки класса. Но потом привыкаешь и входишь во вкус (:
  • Сложный контроль. Дело в том, что если в нескольких трейтах есть метод с одинаковым именем и вы не разрулили этот конфликт, то будет ошибка (правда она отловится в момент сборки, но всё равно неприятно).
  • Что-то ещё из комментариев — уверен, что-нибудь подскажут.

Плюсы:

  • Это новый, другой взгляд на разработку на РНР. Приносит своеобразный кураж, когда пробуешь новый подход, который не просто корректирует маршрут, а полностью меняет дорогу.
  • Элементарны расширение и замена пластов функционала разного уровня. Подключаем трейт в сборку — оппа, у нас новый функционал, о котором наш «мир» ничего не знал. Например, беря классику жанра примеров — синглтон: подключаем трейт реализующий этот функционал и наш мир уже «одиночка». Заменили трейт и уже вместо json’a у нас используется yaml (см. пример под списком)
  • Можно одновременно иметь несколько сборок с разным поведением. Удобно при тестировании: собрали несколько вариантов с разными модулями, запустили тесты и посмотрели, что эффективнее, корректнее и т.п. Кроме того, это может быть полезно при реализации функционала, подразумевающем роли/группы пользователей: для каждой группы можно собрать свой класс (в котором уже не будет проверок на роли и всего сопутствующего).
  • Что-то ещё из комментариев — надеюсь, что-нибудь подскажут.
	trait Json 	{ 		public function getConfig(){...} 	}  	trait Yaml 	{ 		public function getConfig(){...} 	}  	trait Somewhere 	{ 		public function inTheCode(){... $this->getConfig ...} 	} 

Теперь предлагаю перейти к практическому опыту.

У меня имеется два проекта, которые я попробовал выполнить в данной концепции. При разработке я пришёл к следующим выводам/решениям/взглядам:

1. Элементарна поддержка событийной модели.

На данный момент в одном из проектов сделал так: у меня есть простая карта вида:

[ 	'событие' => ['слушатель', 'listener'] ] 

И во время срабатывания события происходит нечто подобное:

if(method_exists($this, $listener)) 	$this->$listener($context) 

Где контекст — это какие-то данные, которые сопутствуют событию.
Т.е. это тот момент, когда чувствуешь профит от того, что всё в одном классе.

2. Попробовал довольно странный метод хранения конфигов — тоже в трейтах.

Т.е. условно это выглядит так:

trait World_Config { 	public $worldConfig = [ 		// какие-то данные 	]; } 

И в настройках сборки я указываю нужный мне конфиг. Это очень удобно по нескольким причинам:

  • По опыту, как правило, в проектах формируются конфиги, которые потом постоянно используются. Соответственно, зачем каждый раз парсить этот конфиг, если можно сразу его подцеплять и использовать.
  • Возвращаясь к ситуации с разными ролями, для каждой роли так удобно подключать свой конфиг.
  • Нам сразу же в момент создания объекта доступны все данные из конфига. На самом деле это открывает большие возможности.

Заключение

Думаю, для начала информации достаточно, чтобы поэкспериментировать (:

Если эта тема будет интересна, то я могу написать ещё несколько статей с детальным описанием одного из своих проектов, в котором опишу реальные боевые условия, в которых приходится разрабатывать в такой концепции.

Если есть вопросы — задавайте, с удовольствием отвечу.

p.s. Если вы знаете какие-то рабочие проекты с подобной реализацией — пишите, буду рад добавить в статью.

Вам интересна данная концепция разработки на РНР?

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Никто ещё не голосовал. Воздержавшихся нет.

ссылка на оригинал статьи http://habrahabr.ru/post/204946/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *