====== Поддержка наследования ====== Пакет ACTIVE_RECORD поддерживает наследование классов, которые хранятся в рамках одной таблицы. Это значит, что вы можете создать несколько классов, которые будут иметь общего родителя и которые будут храниться в одной таблице базы данных. Эти классы могут иметь различные методы по обработке данных и иметь различные связи. Такой тип наследования называется **Single Table Inheritance**. ===== Определение наследуемых классов ===== Для того, чтобы обеспечить возможности по наследованию, вам необходимо лишь добавить в таблицу, в которой хранятся объекты, одно поле. По-умолчанию это поле называется **kind**. Рассмотрим небольшой пример. Рассмотрим другой пример из реального приложения. Существуют объекты - тесты (Test), которые содержат вопросы (Question), на которые пользователи должны давать ответы (Answer). Вопросы существуют различных типов: * вопросы с вариантами ответов (%%VariantsQuestion%%), * вопросы на попадание в заданный диапазон (%%RangedQuestion%%), * вопросы на время (%%TimeQuestion%%), * вопросы на свободный ответ (%%ExtendedQuestion%%). Каждом классу вопросов соответствовал класс ответа, например, %%TimeAnswer%%, %%VariantsAnswer%%, %%ExtendedAnswer%%. В каждом классе ответа реализовывался свой собственный алгоритм подсчета баллов за вопрос. Однако, мы не будем здесь разбирать этот пример полностью, наша цель - показать, какие еще возможности предоставляет пакет ACTIVE_RECORD по работе с наследуемыми объектами. Итак, у нас были следующие таблицы (наиболее значимые): CREATE TABLE `question` ( `id` bigint(20) NOT NULL auto_increment, `question` text NOT NULL, `min_value` float NOT NULL, `max_value` float NOT NULL, `max_time` int(11) NOT NULL, `test_id` varchar(20), `kind` varchar(255), PRIMARY KEY (`id`), KEY `kind` (`kind`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `test` ( `id` bigint(20) NOT NULL auto_increment, `title` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `answer_variant` ( `id` bigint(20) NOT NULL auto_increment, `title` varchar(255) NOT NULL, `question_id` bigint(20) NOT NULL, PRIMARY KEY (`id`), KEY `node_id` (`node_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; Наша модель выглядела следующим образом: class Question extends lmbActiveRecord { protected $_db_table_name = 'question'; protected $_many_belongs_to = array('test' => array('field' => 'test_id', 'class' => 'Test')); } class TimeQuestion extends Question { function start(){...} function stop(){...} function getElapsedTime(){...} } class RangeQuestion extends Question { function isInRange(){...} } class VariantsQuestion extends Question { protected $_has_many = array('variants' => array('field' => 'question_id', 'class' => 'AnswerVariant')); } class ExtendedQuestion extends Question { } class Test extends lmbActiveRecord { protected $_has_many = array('questions' => array('field' => 'test_id', 'class' => 'Question')); } Обратите внимание на определение $_db_table_name в классе Question - это сделано для того, чтобы все дочерние классы "знали", в какую таблицу они должны записываться и не пытались угадать название таблицы самостоятельно. Отметим, что делать Question абстрактным классом нельзя - таковы особенности реализации класса lmbActiveRecord. Еще раз - поле **kind** в таблице question требуется для того, чтобы lmbActiveRecord знал, что объекты, хранимые в данной таблице могут быть различных родственных классов. :!: До релиза пакета ACTIVE_RECORD 0.2.2 (включительно) для указания базового класса применялся атрибут $_base_class, который указывал lmbActiveRecord, какой класс является базовым в иерархии связанных объектов. Начиная с более поздней версии этот атрибут уже не используется, так как lmbActiveRecord определяет базовый класс самостоятельно. :!: Начиная с версии ACTIVE_RECORD 0.3.0 в kind хранится полный путь наследования объекта в виде **Foo|Bar|Zoo|..** - и это позволяет правильно получать все дочерние объекты какого-то родительского класса. Поиск ведется по шаблону LIKE class_path%, то есть для запроса lmbActiveRecord :: find('Question'); сформируется условие WHERE kind LIKE 'Question|%'. ===== Работа с наследуемыми классами ===== Теперь можно попробовать создать экземпляры новых классов. Создадим тест с четырьмя различными вопросами: $test = new Test(); $test->setTitle('Super test'); $test->save(); $question1 = new VariantsQuestion(); $question1->setQuestion('Choose a variant ...'); $question1->addToVariants($variant1); $question1->addToVariants($variant2); $question2 = new RangeQuestion(); $question2->setQuestion('Give a number...'); $question2->setMinValue(10); $question2->setMaxValue(20); $question3 = new TimeQuestion(); $question3->setQuestion('Do something in time ...'); $question3->setMaxTime(1000); $question4 = new ExtendedQuestion(); $question4->setQuestion('Tell us something...'); $test->addToQuestions($question1); $test->addToQuestions($question2); $test->addToQuestions($question3); $test->addToQuestions($question4); $test->save(); Для загрузки объектов можно указывать базовый класс, приведение к нужному классу будет выполнено автоматически. $variants_question = lmbActiveRecord :: findById('Question', $question_id1); echo "Variants question with variants :\n"; foreach($variants_question->getVariants as $variants) echo $operator->getSection()->getTitle() . "\n" [...] $time_question = lmbActiveRecord :: findById('Question', $question_id3); $time_question->start(); // [...] $time_question->stop(); echo "You spent " . $time_question->getElapsedTime() . " seconds"; Если же нужно получить только объекты специального класса, то его название нужно использовать в find()-методе: $time_questions = lmbActiveRecord :: findById('TimeQuestion'); Допустим нам необходимо получить в тесте только вопросы на время (это было одним из функциональных требований). Для этого мы можем задать свойство **class** в массиве $params, который приходит в метод find($params = array()) коллекции. Подробнее о методе find() в разделе [[limb3:ru:packages:active_record:more_on_relations#poisk_ehlementov_v_kollekcijax|"Дополнительная информация по отношениям"]]. Например: $test = new Test($test_id); $time_questions = $test->getQuestions()->find(array('class' => 'TimeQuestion')); ===== Смена поля для хранения информации о классах ===== Вы можете сменить название поля, в котором у вас хранится информация о том, объект какого именно класса хранится в записи таблицы. Для этого вызовите статический метод lmbActiveRecord :: **setInheritanceField($new_field_name)**. Получить текущее имя можно при помощи статического метода lmbActiveRecord :: **getInheritanceField()**.