Автоматический «текучий интерфейс» и ArrayIterator в PHP-моделях

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

Сложно представить достаточно крупный проект, в котором не было бы работы с моделями данных. Скажу больше: по моему опыту около трех четвертых всего кода — это создание, загрузка, изменение, сохранение или удаление записей. Будь то регистрация пользователя, вывод десятка последних статей или работа с админкой — все это мелкая работа с базовыми операциями моделей. И, соответственно, такой код должен писаться и читаться быстро и не должен забивать голову программиста техническими деталями: он (программист) должен думать о логике работы приложения, а не об очередном UPDATE-запросе.

Вместо предисловия


На мой взгляд, чтобы код легко читался, он (помимо, разумеется, стандартов кодирования и понятности алгоритмов) должен быть максимально приближен к естественному языку. Загрузи этот товар, установи ему вот такое название, установи вот такую цену, сохрани. Кроме того, если в коде возможно избежать от повторения, будь то кусок кода или просто название переменной (при работе с одним объектом), то его следует избежать. В моем случае «текучий интерфейс» избавил меня от постоянного нудного копирования имени переменной.

Текучий интерфейс


Логично будет отделить мух от котлет и вынести «текучий интерфейс» в отдельный класс, на случай, если его потребуется использовать не только в моделях:
abstract class Core_Fluent extends ArrayObject {}


Перед тем, как начать писать, я определился, каким я хочу видеть конечный код, который я буду использовать. Получилось вот это:
$instance->load($entity_id)
	->setName('Foo')
	->setDescription('Bar')
	->setBasePrice(250)
->save();

При этом, я хотел, чтобы данные хранились с ключами вида «name», «description», «base_price» (это позволило бы гораздо проще реализовать взаимодействие с БД и этого требовал мой стандарт кодирования).

Для того, чтобы не писать в каждой модели однотипные методы, следует использовать «магические методы» (Magic Methods), в частности, метод __call(). Также можно было использовать методы __get() и __set(), но я пошел путем применения ArrayIterator.

Итак, метод __call, который будет определять, что именно было вызвано и что вообще дальше делать:
...
	// регулярное выражение для преобразования CamelCase в стиль_через_подчеркивания
	// Взял в свое время со StackOverflow, потому как в регулярках не силен
	const PREG_CAMEL_CASE = '/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])/';

	// Массив для хранения самих данных
	protected $_data = array();

	public function __call($method_name, array $arguments = array()) {
		// Первым делом проверяем, подходит ли вообще то, что было вызвано под используемый 
		// шаблон и делим на действие и свойство (setBasePrice тут разделится на set и BasePrice)
		if(!preg_match('/^(get|set|isset|unset)([A-Za-z0-9]+)$/', $method_name, $data)) {
			// И если не подходит, то бросаем исключение
			throw new Core_Exception('Method '.get_called_class().'::'.$method_name.' was not found');
		}
		
		// Затем переводим имя свойства в стандартный вид (BasePrice => base_price) и помещаем в $property
		$property = strtolower(preg_replace(self::PREG_CAMEL_CASE, '_$0', $data[2]));
		
		// Теперь надо понять, что с этим вообще делать
		switch($data[1]) {
			case 'get': {
				// $object->getBasePrice(): возвращаем значение свойства
				return $this->get($property);
			} break;
			
			case 'set': {
				// $object->setBasePrice(): изменяем значение свойства
				return $this->set($property, $arguments[0]);
			} break;
			
			case 'unset': {
				// $object->getBasePrice(): удаляем свойство из объекта
				return $this->_unset($property);
			} break;
			
			case 'isset': {
				// $object->getBasePrice(): проверяем, есть ли это свойство у объекта
				return $this->_isset($property);
			} break;
			
			default: {
				
			}
		}
		// И, если мы сюда дошли, то возвращаем объект, реализуя таким образом "текучий интерфейс"
		return $this;
	}
...


Методы get, set, _isset и _unset


Реализация этих методов не представляет никакой сложности, их действие очевидно из названия:
...
	public function get($code) {
		if($this->_isset($code)) {
			return $this->_data[$code];
		}
		// Вот тут можно бросить исключение, но я предпочел просто вернуть NULL
		return NULL;
	}
	
	public function set($code, $value) {
		$this->_data[$code] = $value;
		return $this;
	}
	
	public function _unset($code) {
		unset($this->_data[$code]);
		return $this;
	}	

	public function _isset($code) {
		return isset($this->_data[$code]);
	}
...


ArrayIterator


Помимо вышеозначенного подхода, я решил добавить возможность работать с объектом и как с обычным ассоциативным (и не только, но это уже другая история) массивом: для этого есть ArrayIterator. Конечно, правильнее было назвать методы, описанные в предыдущем разделе, так, чтобы не пришлось дублировать, но, во-первых, тут уже пришлось думать об обратной совместимости, поскольку был код, использующий эти методы напрямую и его было достаточно много, а во-вторых, на мой взгляд, одно дело — реализация ArrayIterator, а другое — реализация текучего интерфейса.

...
	public function offsetExists($offset) {
		return $this->_isset($offset);
	}
	
	public function offsetUnset($offset) {
		return $this->_unset($offset);
	}
	
	public function offsetGet($offset) {
		return $this->get($offset);
	}
	
	public function offsetSet($offset, $value) {
		return $this->set($offset, $value);
	}
	
	public function getIterator() {
		return new Core_Fluent_Iterator($this->_data);
	}
...

И, соответственно, класс Core_Fluent_Iterator:
class Core_Fluent_Iterator extends ArrayIterator {}


Все. Теперь с любым классом, наследующимся от Core_Fluent доступны такие манипуляции:
class Some_Class extends Core_Fluent {}

$instance = new Some_Class();

$instance->set('name', 'Foo')->setDescription('Bar')->setBasePrice(32.95);

echo $instance->getDescription(), PHP_EOL; // Bar
echo $instance['base_price'], PHP_EOL; // 32.95
echo $instance->get('name'), PHP_EOL; // Foo


// name => Foo
// description => Bar
// base_price => 32.95
foreach($instance as $key => $value) {
	echo $key, ' => ', $value, PHP_EOL;
}

var_dump($instance->issetBasePrice()); // true
var_dump($instance->issetFinalPrice()); // false
var_dump($instance->unsetBasePrice()->issetBasePrice()); // false


Модель


Теперь сама модель, частный случай применения вышеописанного механизма.
abstract class Core_Model_Abstract extends Core_Fluent {}


Для начала необходимо добавить основу для CRUD (создание, загрузка, изменение и удаление). Логика (работа с БД, файлами и чем угодно еще) будет ниже по иерархии, здесь нужно сделать только самое основное:

...
	// Массив измененных свойств, понадобится чуть позже
	protected $_changed_properties = array();

	// Создание. При реализации save() ние по иерархии можно добавить проверку на
	// существование и вызывать этот метод автоматически, в случае если идентификатор
	// не найден в базе (или где угодно еще)
	public function create() {
		return $this;
	}
	
	// Загрузка
	public function load($id) {
		$this->_changed_properties = array();
		return $this;
	}
	
	// Загрузка из массива
	public function loadFromArray(array $array = array()) {
		$this->_data = $array;
		return $this;
	}

	// Сохранение
	public function save() {
		$this->_changed_properties = array();
		return $this;
	}
	
	// Удаление
	public function remove() {
		return $this->unload();
	}

	// Выгрузка из памяти
	public function unload() {
		$this->_changed_properties = array();
		$this->_data = array();
		return $this;
	}

	// Конвертация объекта в массив
	public function toArray() {
		return $this->_data;
	}
...

И, наконец, переопределим set(), добавив массив измененных свойств
...
	public function set($code, $value) {
		$this->_changed_properties[] = $code;
		return parent::set($code, $value);
	}
...

Теперь от этого класса можно наследовать различные адаптеры к базам данных, файлам или API, от которых, в свою очередь, наследовать уже конечные модели данных.

Полный код всех трех файлов под спойлером.
Полный код всех трех файлов
Core/Fluent.php
<?php
abstract class Core_Fluent extends ArrayObject {
	const PREG_CAMEL_CASE = '/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])/';
	
	protected $_data = array();
	
	public function __call($method_name, array $arguments = array()) {
		if(!preg_match('/^(get|set|isset|unset)([A-Za-z0-9]+)$/', $method_name, $data)) {
			throw new Core_Exception('Method '.get_called_class().'::'.$method_name.' was not found');
		}
	
		$property = strtolower(preg_replace(self::PREG_CAMEL_CASE, '_$0', $data[2]));
	
		switch($data[1]) {
			case 'get': {
				return $this->get($property);
			} break;
				
			case 'set': {
				return $this->set($property, $arguments[0]);
			} break;
				
			case 'unset': {
				return $this->_unset($property);
			} break;
				
			case 'isset': {
				return $this->_isset($property);
			} break;
				
			default: {
	
			}
		}
		return $this;
	}
	
	public function get($code) {
		if($this->_isset($code)) {
			return $this->_data[$code];
		}
		return NULL;
	}
	
	public function set($code, $value) {
		$this->_data[$code] = $value;
		return $this;
	}
	
	public function _unset($code) {
		unset($this->_data[$code]);
		return $this;
	}
	
	public function _isset($code) {
		return isset($this->_data[$code]);
	}
	
	/**
	 * Implementation of ArrayIterator
	 */
	public function offsetExists($offset) {
		return $this->_isset($offset);
	}
	
	public function offsetUnset($offset) {
		return $this->_unset($offset);
	}
	
	public function offsetGet($offset) {
		return $this->get($offset);
	}
	
	public function offsetSet($offset, $value) {
		return $this->set($offset, $value);
	}
	
	public function getIterator() {
		return new Core_Fluent_Iterator($this->_data);
	}
}
?>


Core/Fluent/Iterator.php
<?php
class Core_Fluent_Iterator extends ArrayIterator {}
?>


Core/Model/Abstract.php
<?php
abstract class Core_Model_Abstract extends Core_Fluent {
	protected $_changed_properties = array();
	
	public function set($code, $value) {
		$this->_changed_properties[] = $code;
		return parent::set($code, $value);
	}
	
	public function create() {
		return $this;
	}
	
	public function load($id) {
		$this->_changed_properties = array();
		return $this;
	}
	
	public function loadFromArray(array $array = array()) {
		$this->_data = $array;
		return $this;
	}
	
	public function save() {
		$this->_changed_properties = array();
		return $this;
	}
	
	public function remove() {
		return $this->unload();
	}
	
	public function unload() {
		$this->_changed_properties = array();
		$this->_data = array();
		return $this;
	}
	
	public function toArray() {
		return $this->_data;
	}
}
?>



Вместо заключения


Получилось достаточно объемно, но, в основном, из-за кода. Если эта тема интересна, то я могу описать реализацию коллекции (некое подобие массива записей с возможностью загрузки с фильтрацией и коллективных (batch) действий) на этом же механизме. И коллекции, и эти модели взяты из разрабатываемого мной фреймворка, поэтому их правильнее рассматривать в комплексе, но я не стал перегружать и без того объемную статью.
Разумеется, буду рад услышать ваше мнение или аргументированную критику.

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 45

    0
    Интересное решение. Выложите исходный код фреймворка, если это возможно, на github.
      0
      Я собираюсь его немного переписать, исправив некоторые моменты, и после этого он там обязательно появится.
    0
    redbeanphp.com/ полноценная orm построенная на этом принципе
      0
      Если я вас правильно понял, под принципом вы имеете в виду CRUD (Create, Read, Update, Delete), он используется в подавляющем большинстве ORM и фреймворков. Если я неправильно понял, то поясните, что именно за принцип вы имели в виду.
        0
        текучий интерфейс
          +1
          Посмотрел, бегло ознакомился, текучего интерфейса не нашел. Может быть просто в примерах нет, но на всякий случай уточню: текучий интерфейс — это когда метод объекта, проделав какие-то манипуляции, возвращает $this, позволяя строить длинные цепочки из действий, например:
          $string->toLowerCase()->upperCaseFirst()->replace('_', ' ')->print();
            0
            Спасибо, что разъяснили. Я думал под текучестю имеется ввиду наличие контейнера и отсутсвие строгих полей объекта
              +1
              их еще цепными методами называют (chained methods)
        –1
        Поздравляю, вы изобрели active record :)
          +1
          Верно, много где это используется. Например в Yii.
          +1
          Текучий интерфейс это конечно же хорошо, но как тогда с валидацией данных?
          Предположим используем конструкцию
          $instance->set('name', 'Foo')->setDescription('Bar')->setBasePrice(32.95);
          

          Представим, что на каждое из свойств есть особенный тип ограничения basePrice > 10, description > 10 символов, а name только на латинице.
          Как модель проинформирует об ошибке? Бросит Exception, а потом мы в switch будем перебирать тип исключения чтобы отобразить пользователю? То же самое с create, save, remove, load Как быть в таком случае?

          Второй аспект: выходит так, что базовая модель перенасыщена «магией» (аж 3 варианта ввода одних и тех же данных) + еще и регулярка на каждый _call, таким образом все остальные (дочерние) модели будут подобного толка. Магия очень дорога, по крайней мере пока.
          Возможно стоит оставить 1 вариант для унификации ввода и облегчения жизни cpu?
          Ведь подобная архитектура приведет к серьезному проседанию производительности. Может выйти так, что наш маленький фреймворк работает не на много быстрее большого, но оптимизированного в этом плане, а то и хуже.

          Стоит ли обилие вариантов ввода 3х кратной потери производительности?
            0
            Спасибо за развернутый комментарий.

            Да, определенные проблемы с производительностью могут иметь место. По большому счету, для этого есть прямой set() без регулярок. Если нужна дополнительная логика, то можно переопределить тот же метод setDescription() в самой модели, вызывая в конце set('description', $value) или parent::setDescription(). Но в целом, да, вы правы, здесь производительность жертвуется в угоду удобочитабельности и наглядности.

            По поводу ошибок в данных я лично представляю себе только вариант с исключениями, руководствуясь тем, что если произошла критическая ошибка, то программа должна остановиться, выбросить исключение и «спросить» у программиста, что же делать в подобной ситуации. Для этого я создам три десятка исключений на каждую модель, наследующихся друг от друга, и в try… catch… catch… catch буду указывать, что нужно делать в подобной ситуации. Для некритических ошибок подошел бы вариант с неким подобием предупреждений (warnings), но на эту тему я пока, к сожалению, не думал.
              0
              Пример валидации данных из фреймворка Kohana, на мой взгляд это самый удобный вариант.
              try
              {
                  $user = ORM::factory('User');
                  $user->username = 'invalid username';
                  $user->save();
              }
              catch (ORM_Validation_Exception $e)
              {
                   $errors = $e->errors();
              }
              
                0
                Вопрос был про текучий интерфейс. Как раз и подразумевалось, что вариант подобный предложенному вами намного удобнее.
                  0
                  Если честно я не особо понимаю разницу в отлове исключения в предложенном мной варианте, и при использовании текучего интерфейса.
                  При сохранении просто делается валидация всех полей, и создается исключение определенного типа, в который вложены все ошибки в виде массива, как в примере выше.
              +2
              он (программист) должен думать о логике работы приложения, а не об очередном UPDATE-запросе.

              Для этого существуют ORM. Мой выбор Doctrine, но Propel также очень и очень хорош.

              На мой взгляд, чтобы код легко читался, он (помимо, разумеется, стандартов кодирования и понятности алгоритмов) должен быть максимально приближен к естественному языку.

              Чтобы код легко читался однозначно следует избегать использования «магических» методов. Даже не учитывая проблемы с производительностью (особенно в предложенном варианте с двумя регулярными выражениями внутри), магия усложняет чтение кода. В чем проблема написать get и set методы для нужных свойств модели? Большинство IDE позволяют это делать автоматически.
                0
                По поводу усложнения чтения кода я не соглашусь, потому что, на мой взгляд, куча проверок перед изменением записи (если использовать запись «сырых» данных, используя, например, $instance->price = 12.95) нисколько не облегчает чтение кода, и, кроме того, зачастую приводит к его дублированию. А если использовать генерируемые методы, то внешне (в логике приложения) это не будет отличаться от данного подхода ничем, кроме этих самых двух регулярок, которые, при наличии дополнительной логики при установке значения, не используются.

                Этот механизм не претендует на то, чтобы заткнуть за пояс известные ORM. Это просто иллюстрация еще одного подхода, который, на мой взгляд ии по моему опыту, достаточно удобен в использовании.
                  +1
                  если использовать запись «сырых» данных, используя, например, $instance->price = 12.95

                  Я и не рекомендую использовать публичные свойства. Причины (понятность, дублирование) вы сами указали.

                  Поясню почему я против «магии» в данном варианте. Текущая реализация мешает бизнес-логику (наши модели) с технической реализацией, что чревато в дальнейшем сложноподдерживаемым кодом и трудноуловимыми ошибками. Я стараюсь оставлять модели максимально чистыми, не подверженными технической реализации. Это позволит впоследствии изменить одну часть не затрагивая другую. Например поменять одну ORM систему на другую, или вообще отказаться от ORM в угоду производительности в «узких» местах.

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

                    При смене одной ORM на другую в любом случае придется переделывать бизнес-логику, либо нужно делать доступ к данным «точно такой же, как в %ORM-name%». Если вы имеете в виду, к примеру, переход с MySQL на PostgreSQL, то на этот случай предусмотрены адаптеры подключения к БД и переход в данном случае проходит вполне безболезненно, заменяя лишь класс, от которого наследуется модель.
                      0
                      При смене одной ORM на другую в любом случае придется переделывать бизнес-логику

                      Бизнес-логика не должна зависеть от ORM (если только не что-то вроде phpmyadmin пишем). При хорошей MVC архитектуре, переделывать при смене ORM (или вообще отказа от РСУБД и перехода на NoSQL, файлы или какое-нибудь облачное хранилище) нужно только контроллер.
                        0
                        Да, я скорее имел в виду контроллер. К сожалению, путаюсь в терминологии.
                        В данном случае, как я уже сказал, менять не нужно вообще ничего при условии, что названия свойств остаются теми же. В этой статье я лишь немного коснулся темы адаптеров, но вообще они позволяют легко и просто переключаться между работой с разнообразными хранилищами, будь то база данных, реляционная или нет, файлы, что угодно. Главное, чтобы оттуда можно было прочитать данные, туда можно было записать данные и оттуда можно было удалить данные.
                          0
                          Наоборот, вся бизнес логика должна быть в моделях, а то про что говорите вы называется ТТУК
                            0
                            Совершенно необязательно. Логика может быть в различных сервисах, а контроллер только дергает нужные. Чаще проще сделать маленький сервис с необходимыми зависимостями, чем пробрасывать зависимости в объект модели.
                  0
                  Хочу придраться к вашему конечному варианту: в реальном мире товар сам себя на полку не кладет. Не должно у товара быть метода save, как мне кажется. Для этого есть менеджер БД, который и должен его сохранять.
                    0
                    Если честно, не совсем понял логику. Вы имеете в виду, что должен быть некий централизованный объект, который умеет сохранять любую запись в базу? А если мне при сохранении именно товаров нужно сделать еще что-то, в лог запись добавить, например?
                      0
                      Вариант 0: слелать kju gj instance_of :)
                      Вариант 1: для каждого класса сущности сделать класс хранилища (репозитория)
                      Вариант 2: сделать для CRUD статические методы.

                        0
                        Как пример, посмотрите на Doctrine ORM. Сам объект строки только хранит данный.
                          0
                          Там вообще как такового объекта строки или записи нет. Объект мапится на БД, но правила мапинга вовсе не обязаны 1:1 к структуре БД относится.
                        0
                        Это просто 2 разных шаблона проектирования, тот что описывает автор называется Active Record, а то что предлагаете вы называется Data Mapper.
                        0
                        __call — это медленно! Где-то была статья недавно, не могу найти. Там производился анализ работы с вызовом методов через __call и обычным вызовом. А здесь, где-то в середине, есть рассуждение про орм в принципе, и почему это плохо. Про spl я вообще молчу…

                        он (программист) должен думать о логике работы приложения, а не об очередном UPDATE-запросе.

                        хороший программист должен думать обо всем.
                          0
                          Не все разрабатывают хайлоад. Хотя лично я __call тоже избегаю, но не из-за скорости, а из-за сложности чтения и поддержки.

                          P.S. Слушание подкастов напоминает мне сказки на грампластинках или радиоспектакли в СССР. В дестве мог минут 40 сконцентрироваться, сейчас нет, или заспыпаю, если глаза закрываю, или читать что-то начинаю и нить теряю.
                            0
                            Никто не спорит, что использование __call медленнее, а дополнительные системы вроде ORM тоже дают определенную нагрузку. Но на другой чаше весов скорость написания кода, удобство его поддержки и чисто субъективная понятность. Грубо говоря, можно все это написать на ассемблере, и это будет определенно быстрее обычных вызовов.

                            Хороший программист должен думать обо всем, но не все являются хорошими программистами. Зачастую лучше предоставить вот такую «автоматическую коробку передач», которая позволяет добраться из точки А в точку Б не требуя от программиста погружаться в дебри реализации. лично мне спокойнее на душе, когда я знаю, что мой коллега ничего не забудет, потому что я это уже предусмотрел в самой модели. Но я, разумеется, понимаю, что это нарушение заповеди «не храни логику в модели» :)
                              0
                              Но я, разумеется, понимаю, что это нарушение заповеди «не храни логику в модели» :)

                              Некоторую логику нужно хранить в модели. Насчёт логики хранения споры идут постоянно — уж очень народу нравится паттерн ActiveRecord и не нравится использовать его чисто как DTO.
                                +1
                                А можно узнать про принцип «не храни логику в модели»? Я только слышал про обратный принцип, что вся логика работы должна быть в модели.
                                  –3
                                  По MVC в модели должны быть только данные, а вся логика должна быть в контроллерах.
                                    0
                                    По моему вы ошибаетесь, приведу в пример выдержку из вики
                                    Выдержка
                                    Начинающие программисты (особенно в веб-программировании, где аббревиатура MVC стала популярна) очень часто трактуют архитектурную модель MVC как пассивную модель MVC. В этом случае модель выступает исключительно совокупностью функций для доступа к данным, а контроллер содержит бизнес-логику. В результате код моделей по факту является средством получения данных из СУБД, а контроллер представляет собой типичный модуль, наполненный бизнес-логикой, или скрипт в терминологии веб-программирования. В результате такого понимания MVC разработчики стали писать код, который Pádraic Brady, известный в кругах сообщества Zend Framework, охарактеризовал как ТТУК — «Толстые тупые уродливые контроллеры» (Fat Stupid Ugly Controllers):
                                    Среднестатистический ТТУК получал данные из БД (используя уровень абстракции базы данных, делая вид, что это модель) или манипулировал, проверял, записывал, а также передавал данные в вид. Такой подход стал очень популярен потому, что использование таких контроллеров похоже на классическую практику использования отдельного php-файла для каждой страницы приложения.

                                    Но в объектно-ориентированном программировании используется активная модель MVC, где модель — это не только совокупность кода доступа к данным и СУБД, а вся бизнес-логика. В свою очередь, контроллеры представляют собой лишь элементы системы, в чьи непосредственные обязанности входит приём данных из запроса и передача их другим элементам системы. Только в этом случае контроллер становится «тонким» и выполняет исключительно функцию связующего звена (glue layer) между отдельными компонентами системы.

                                      0
                                      Хм, да, похоже я ошибся.
                              0
                              $instance->load($entity_id)
                                  ->setName('Foo')
                                  ->setDescription('Bar')
                                  ->setBasePrice(250)
                              ->save();
                              

                              Какая IDE может так выравнивать?
                                +2
                                phpStorm
                                  0
                                  У меня такой каши нет. Специально проверил.
                                0
                                И как на счёт кодогенерации?
                                Плюсы:
                                * Работает автодополнение (конечно, в Вашем случае тоже можно заставить работать автодополнение, но тут мы или вводим вручную или опять же генерируем)
                                * Используется минимум магических функций

                                Минусы:
                                * Нужно реализовать.
                                  0
                                  Кодогенерация — это один из выходов. Другой выход — это как раз использование магии. Оба подхода имеют свои плюсы и минусы. Плюс магического, в частности, в том, что не нет строгого набора данных.

                                  Вот пример: есть, допустим, стороннее API и есть модель, которая с ним работает. API обновилось, добавилось новое свойство. Его можно использовать сразу же, не редактируя модель. Это полезно, когда разработкой фреймворка занимается один человек, а написанием бизнес-логики — другой.
                                    0
                                    Плюс магического, в частности, в том, что не нет строгого набора данных.

                                    Это и плюс, и минус. В последнее время мои программы на PHP приобретают всё больше Java стиль — явные объявления типов где только можно, ассерты с instance_of где нельзя и т. п. Динамическими возможностями пользуюсь с большой неохотой, когда они приносят явное упрощение кода, потому как практически всегда они снижают читаемость и поддерживаемость.

                                Only users with full accounts can post comments. Log in, please.