Доброго дня Товарищи!Я тут пишу свои всякие поделки, и вот заинтересовался вопросом, а правильный ли я выбрал подход к созданию экземпляра объекта по его ТИПУ? Поясню это на примере:
Допустим, есть у меня к несколько разных вариаций от общего предка, к примеру виды Документов:
//Общий предок
Class Document {
const DOC_DOGOVOR = 1;
const DOC_PISMO = 2;static public getDocByType(int $docType){
switch($docType){
case self::DOC_DOGOVOR:
return new Doc_Dogovor();
break;case self::DOC_PISMO:
return new Doc_Pismo();
break;
}return false;
}
}Class Doc_Dogovor extends Document {
protected static $docType = Document::DOC_DOGOVOR;
//Своя какая-то кухня с свойствами и методами такого
//типа документов ...
}Class Doc_Pismo extends Document {
protected static $docType = Document::DOC_PISMO;
//Своя какая-то кухня с свойствами и методами такого
//типа документов ...
}//Получить объект нужного типа (из БД к примеру тип приходит)
$docObj = Document::getDocByType($neededTypeFromDB);
Так вот вопрос собственно в том, насколько оправданно или наоборот плохо и как лучше всего такой тип задачи реализовывать?
А задача значит получение экземпляра объекта указанного типа. Видел, что во всяких автозагрузчиках классов сейчас используется динамическое их получение условно такой схемой что если запрошенного класса нет, он подгружается по имени и дальше также создаётся объект класса, название которого в переменной$docObj = new $objClassName();
Ещё дополню, что почему не стал использовать вариант с получением условно по имени класса в переменной, у меня ощущение, что такое динамическое получение объекта чем то сродни exec(), а такого рода финты вроде не совсем правильно использовать. А варианты типов объектов класса свё равно же создаются и их можно сразу в перечисление switch case добавлять.
> Ещё дополню, что почему не стал использовать вариант с получением условно по
> имени класса в переменной, у меня ощущение, что такое динамическое получение
> объекта чем то сродни exec(), а такого рода финты вроде не
> совсем правильно использовать. А варианты типов объектов класса свё равно же
> создаются и их можно сразу в перечисление switch case добавлять.
>чем то сродни exec(), а такого рода финты вроде не совсем правильно использоватьeval и иже с ним плохи в первую очередь из-за того, что в них поступает непроверенный пользовательский код. Если оный в него не поступает, жить можно, вон, один язык на eval опирается как на основной способ расширения синтаксиса и ничего.
> eval и иже с ним плохи в первую очередь из-за того, что
> в них поступает непроверенный пользовательский код. Если оный в него не
> поступает, жить можно, вон, один язык на eval опирается как на
> основной способ расширения синтаксиса и ничего.Да, точно спутал я exec с eval'ом. Предубеждение у меня к eval'у. Совсем противоестественно использовать эту конструкцию и никогда её не использую.
>А варианты типов объектов класса свё равно же создаются и их можно сразу в перечисление switch case добавлять.Вот этот вариант подойдёт как решение наполовину (если вы это будете делать не в основном классе, а в подклассах на этапе инициализации класса или опосредованно через базовый класс, но динамически и опять же через инициализацию подкласса, базовый класс не должен знать все подклассы - не в курсе, как там оно именно делается в PHP, но механизм должен быть), но проще было бы динамически получать имя класса - если каких-то дополнительных трудностей нет.
А вообще, зачем вам получать потомков класса через базовый класс, а не через конструктор собственно потомка?
> А вообще, зачем вам получать потомков класса через базовый класс, а не
> через конструктор собственно потомка?Потому что условно в БД хранится тип объекта (к примеру число objType), и мне чтобы через конструктор потомка получать экземпляр объекта - мне надо имя класса этого потомка знать по его objType? И потом делать этот озвученный "динамический трюк" $objItem=new $objClassName();
А как я узнаю, что в переменной $objClassName - корректное название класса?
Эта вот неоднозначность меня смущает. Я не могу понять - как получается в случае динамического варианта, что имя класса - экземпляр объекта которого я хочу получить - верное?
>А как я узнаю, что в переменной $objClassName - корректное название класса?А оно надо? Кто пользователь ORM - программист или внешний пользователь, который про эти имена классов вообще знать ничего не должен?
> А оно надо? Кто пользователь ORM - программист или внешний пользователь, который
> про эти имена классов вообще знать ничего не должен?Пользователь ORM - получается программист, т.е. Я. Она (типа ORM, хотя конечно нет) для построения логики с сущностями которые реализуют объекты в приложении. Но в любом случае неважно должен внешний пользователь знать про имена классов или нет, как для "динамического" варианта я должен соотнести objTypeID из базы с именем класса, чтобы выполнить итоговою строку
$objItem = new $objClassName();? Варианта вижу 2:
Или в коде формировать связь objTypeID => objClassName, т.е.$objTypeData = [
Doc_Dogovor::OBJ_TYPE => 'Doc_Dogovor',
Doc_Pismo::OBJ_TYPE => 'Doc_Pismo',
];
......
$objItem = new $objTypeData[$objTypeID]();
Либо в самой БД хранить не objTypeID - а сразу имя класса, но вроде это совсем грязь, хранить куски кода в БД.Или я чего-то не понимаю?
>[оверквотинг удален]
> //Получить объект нужного типа (из БД к примеру тип приходит)
> $docObj = Document::getDocByType($neededTypeFromDB);
>
$docObj = new $objClassName();
Дяденька, а зачем вы диспатчите типы через enum, как будто у вас algebraic data types вместо классов?
> Дяденька, а зачем вы диспатчите типы через enum, как будто у вас
> algebraic data types вместо классов?Я вообще не понял вашего словосочетания. Что значит, что я диспатчу? enum - это как раз реализация через их перечисление в switch {case..}?
А как правильней?
>Так вот вопрос собственно в том, насколько оправданно или наоборот плохо и как лучше всего такой тип задачи реализовывать?А это вам как зайдёт, вот вы изобрели в пхп типы данных из хаскеля - осваивайте, укладывая мысль в них.
> А это вам как зайдёт, вот вы изобрели в пхп типы данных
> из хаскеля - осваивайте, укладывая мысль в них.С хаскелем не знаком кроме как слышал название этого языка. Можете пожалуйста пояснить, что я изобрёл и во что мне надо укладывать мысль?
>С хаскелем не знаком кроме как слышал название этого языка.Ознакомьтесь на досуге, такая штука есть во всех ML-подобных языках, Haskell, Ocaml, Standard ML и так далее. Заодно расширите кругозор и сможете писать на своём языке так, как раньше не писали, если в функциональном стиле не пишете (но тут было бы проще ознакомиться с Scheme, я учил ФП с него, правда вот алгебраических типов в нём нет).
Вы изобрели, грубо говоря, вот это:
data Point = Point2D Double Double | Point3D Double Double DoubleВ хаскеле это объявление записи по имени Point, у которой есть два варианта: Point2D из двух координат типа Double и Point3D из трёх координат. Тип Point закрытый - его нельзя расширить, при этом вся работа с данными в основном заворачивается на тип Point, а не на подтипы. В языках же с классами и их наследованием класс, как правило, открытый - на основе одного класса вы можете сделать очень много потомков и подпотомков, при этом основные методы родительского класса должны работать при этом без особых изменений. И вот своим case по тегам подтипов в родительском классе вы нарушаете наследование без проволочек, превращая тип в закрытый, как в хаскеле. Разумеется, после этого вам и придётся со временем всё больше работать со своим типом так, как будто он нерасширяемый.
> А это вам как зайдёт, вот вы изобрели в пхп типы данных
> из хаскеля - осваивайте, укладывая мысль в них.С хаскелем не знаком кроме как слышал название этого языка. Можете пожалуйста пояснить, что я изобрёл и во что мне надо укладывать мысль?
сделай обычный class Document. От него наследуются class Dogovor и class Pismo (еще обзови их class DoroBop и class nuCbMo, раз так фанатеешь от русских названий - сарказм на всякий случай). Класс Document при этом не должен знать о существовании своих подклассов и никак их не упоминать. Далее создай в совершенно отдельном месте функцию, которая принимает $typeFromDb и делает по нему return new КонкретныйКласс, можно через switch.
> сделай обычный class Document. От него наследуются class Dogovor и class Pismo
> (еще обзови их class DoroBop и class nuCbMo, раз так фанатеешь
> от русских названий - сарказм на всякий случай). Класс Document при
> этом не должен знать о существовании своих подклассов и никак их
> не упоминать. Далее создай в совершенно отдельном месте функцию, которая принимает
> $typeFromDb и делает по нему return new КонкретныйКласс, можно через switch.Если что, на самом деле эти Документы, Договора и Письма - чистая выдумка для иллюстрации. С моей текущей задачей конкретно такие сущности никак не связаны.
По поводу организации функцией, а не в родительском классе - в чём у такого подхода принципиальная выгода или "правильность"?
> По поводу организации функцией, а не в родительском классе - в чём
> у такого подхода принципиальная выгода или "правильность"?Класс Document не должен знать о существовании своих подклассов, т. к. это открытый класс. Я могу написать по месту ад-хок class Issue1488_Document extends Document специально для юнит-теста, и мне не придется вводить фуфловый $docType = 1488, которого в базе естессно не будет. Функция -- это пример для простейших систем без подгрузки кода по требованию. Я могу представить себе систему, в которой class LetterDocument подгружается только тогда, когда он реально нужен. В этой системе модуль "поддержки писем" зарегистрировал бы docType под номером 420 и указал бы, что "если встретишь docType = 420, то загрузи такой-то класс из такого-то файла, а я гарантирую, что это будет подкласс от Document". Похожим образом делает линукс с драйверами: встретив PCI-устройство, линукс смотрит в свой modalias на предмет того, нет ли модуля, предоставляющего драйвер для PCI-устройства с таким-то идентификатором, и если есть - загружает его.
Коллеги, я в растерянности. Такое впечатление, что вы вернулись к бредовым идеям самомодифицирующегося кода.А ничё, что вас там ждёт cyclomatic complexity?
> Коллеги, я в растерянности. Такое впечатление, что вы вернулись к бредовым идеям
> самомодифицирующегося кода.
> А ничё, что вас там ждёт cyclomatic complexity?Уважаемый АССА, уточните пожалуйста, что вы имеет в виду? Самомодифицирующийся код - это в смысле динамическая подгрузка классов или наоборот фабрика которую я начал обсуждать?
Самомодифицирующийся код - это когда ты на асме берёшь и меняешь инструкции своей собственной программы.
> Самомодифицирующийся код - это когда ты на асме берёшь и меняешь инструкции
> своей собственной программы.Ну уж читать и википедию листать я и сам умею, и всё это конечно сразу после АССА'вого коммента прочитал. Я не понял как это к обсуждению относится. Про это и спросил.
Фабричный метод с маппингом - подход имеющий право на жизнь. Вопрос в том, для чего это применять.Очевидно, что у вас есть некая универсальная форма, которая посылает ид типа создаваемого документа. И дальше вы создаете документ исходя из его ид. И в базу потом пишете с тем же самым ид.
Вместо этого можно для каждого типа документа сделать свои формы, тип создаваемого документа разруливать по маршруту обработчика, ид для базы держать в константе класса и работать с ним только при записи в базу - то есть, в контроллере вообще никогда.
> Фабричный метод с маппингом - подход имеющий право на жизнь. Вопрос в
> том, для чего это применять.
> Очевидно, что у вас есть некая универсальная форма, которая посылает ид типа
> создаваемого документа. И дальше вы создаете документ исходя из его ид.
> И в базу потом пишете с тем же самым ид.
> Вместо этого можно для каждого типа документа сделать свои формы, тип создаваемого
> документа разруливать по маршруту обработчика, ид для базы держать в константе
> класса и работать с ним только при записи в базу -
> то есть, в контроллере вообще никогда.Не хотел в нудные подробности углубляться, но если это поможет пониманию и оконкретит что главное как лучше это реализовать, то вот, опишу более полно и близко к оригиналу:
Есть такая сущность, как Статья. Это сущность из которой будет формироваться условно страница с самой этой Статьёй:
//Отображение её в БД
TABLE Article (
id SERIAL PRIMARY KEY,
title CHAR(20) NOT NULL
)
Статья состоит из кусков разных типов объектов - Частей Статьи.
//Отображение Частей Статьи в БД
TABLE ArticleParts (
id SERIAL PRIMARY KEY,
artID BIGINT UNSIGNED NOT NULL,
partType TINYINT UNSIGNED NOT NULL,
partOrder TINYINT UNSIGNED NOT NULL,
FK(artID) => Article(id)
)
Так вот поле partType - это как раз тот самый objTypeID по которому мы понимаем Часть Статьи какого типа это:
//Общий предок Частей Статьи
Class ArticlePart {
const PART_TEXT; //Чисто текстовый блок
const PART_IMG_GALLERY; //Набор картинок в блоке
const PAT_POLL; //Блок с голосованием
.......//Получить объект нужного типа
static public & getPartObjByType($objType){
$objItem = false;switch($objType){
case ...:
$objItem = ....;
break;
.... ......
}
}
}//Классы самих Частей Статьи
Class ArticlePart_TEXT extends ArticlePart {
static const OBJ_TYPE = ArticlePart::PART_TEXT;//У каждого вида Части Статьи свой способ оформления в БД,
//со своими таблицами и связями и пр.
public function DB_Save(){
.....
}//Тут к примеру у каждого вида Части Статьи своя генерация HTML под него
public function getHTML(){
.....
}
}И условно статью предполагается создавать из таких вот блоков разных типов. Они могут быть интерактивными, каждый из блоков, со своей связанной с пользователем информацией и пр.
А уже как у статьи в общем может быть там пагинация и прочая херня. Короче велосипед, но мой и хочу чтобы колёса сразу квадратными не выбирались :)
Вроде понятно. У вас контроллер компонует результаты дочерних контроллерчиков. Можно имена классов просто в базу сохранять, без искусственных partType. Выборок по этим ид нет же?
> Вроде понятно. У вас контроллер компонует результаты дочерних контроллерчиков. Можно имена
> классов просто в базу сохранять, без искусственных partType. Выборок по этим
> ид нет же?Блин... Ну вот сама принципиально идея сохранять имена классов в БД...
Ну т.е. условно есть скептическое отношение к языку basic, оператору goto, к использованию непроверенного пользовательского ввода во всякого рода SQL-инъекциях, к использованию конструкции eval, не является ли использование данных из БД в качестве исполняемого кода - плохой практикой?
Ведь если в БД хранится имя класса, то мы должны использовать что-то типа:$objItem = $stringFromDB();. Т.е. как я вижу это - есть концепция "динамического кода", когда сам код программы - неясен на момент исполнения, и во время него более того тоже меняется.
Т.к. в процессе исполнения получается сам код программы становится результатом его работы.
А есть условно "статический код", т.е. в ближайшем приближении - код который можно скомпилировать в исполняемый файл и запустить сам по себе. Всякие eval() и $objFromString() вроде бы представляют собой часть динамической составляющей.Вот я и хочу разобраться, насколько это имеет смысл. Я так понял, что среди плюсов динамического подхода: Проще реализация - исключаем часть "конечного автомата по выбору класса для Объекта"
Минусы у динамики - Смешение кода и данных, т.к. код для создания объекта берётся в любом случае из внешнего источника (БД или Роутинг из URL), также я думаю к минусам можно отнести скорость исполнения (если динамически инклудить файлы с описанием класса для Объекта)
Плюсы и минусы у Статики - соответственно противоположные.
Так ли это? Какие ещё есть варианты реализации таких вот как Товарищ выше описал "Контроллеров" и их "Контроллерчиков"?
> Блин... Ну вот сама принципиально идея сохранять имена классов в БД...Вы можете туда интерфейс сохранять, а не конкретный класс. И через DI контейнер получать конкретную реализацию. Это вот именно то, что вы хотите в плане поведения, на самом деле.
Айдишки искусственны, удобства от них имеют косвенный характер. Будь у вас документо-ориентированная БД и вам бы в голову не пришло их использовать.Хранить в базе код и эвалом исполнять - это фактически serverless концепция, вроде AWS Lambda. Так тоже можно! Любые извращения возможны. Если вам доставляет разбираться в этих концепциях, писать свои реализации - это одна история. Если нужно сделать, чтобы работало (и забыть) - другая. Я подозреваю, что необходимости что-то делать с кодом у вас вообще нет.
> Вы можете туда интерфейс сохранять, а не конкретный класс. И через DI
> контейнер получать конкретную реализацию. Это вот именно то, что вы хотите
> в плане поведения, на самом деле.Пожалуйста объясните, что такое DI контейнер? Просто пример в несколько строчек для иллюстрации. Я абсолютно не понимаю, о чём речь.
> Я подозреваю, что необходимости что-то делать с кодом у вас вообще нет.Ну как... Поддерживать его, развивать логику и функционал на основе этой "фабрики объектов". Это, если я правильно понял суть реплики, не просто код для изучения концепции с теоретической целью. Цель вполне себе ясная, а этот вопрос встал на этапе реализации некоторых сущностей.
> Пожалуйста объясните, что такое DI контейнер? Просто пример в несколько строчек для
> иллюстрации. Я абсолютно не понимаю, о чём речь.На пальцах - это штука, которая знает, как инстанцировать объект указанного вами класса, чтобы вы могли не заниматься этим в своем коде. Дергаете ее либо вручную, либо она автомагически создает для вас объекты в аргументы методов ваших классов.
https://laravel.com/docs/10.x/container
https://symfony.com/doc/current/components/dependency_inject...> Ну как... Поддерживать его, развивать логику и функционал на основе этой "фабрики
> объектов". Это, если я правильно понял суть реплики, не просто код
> для изучения концепции с теоретической целью. Цель вполне себе ясная, а
> этот вопрос встал на этапе реализации некоторых сущностей.Вы могли бы взять любой из фреймворков, где уже все есть, и не создавать собственные реализации, эволюционируя от более примитивных концепций к более продвинутым. Процесс увлекательный, не спорю.
Надо не играть в бабу Вангу и чётко спросить:>Я тут пишу свои всякие поделки
В чём смысол поделки, самопальная ORM?
> Надо не играть в бабу Вангу и чётко спросить:
>>Я тут пишу свои всякие поделки
> В чём смысол поделки, самопальная ORM?Нет, смысл самой поделки не ORM, смысл того что я спрашиваю - некое подобие ORM, но оно не самоцель, а лишь часть в моей общей замути. Спрашиваю, чтобы на этапе пока не накодил много объектов выбранным (и опробованным) способом который в вопросе и описал, и не пришлось потом всё перелопачивать из-за того, что выбранный способ унылый.