Как в PHP из массивов сделать объекты с подсказками?
Когда надоело запоминать ключи массивов и хочется пользоваться подсказками любимого редактора кода на помощь приходит PHPDoc и немного смекалки.
Недавно я очень близко познакомился с TypeScript и познал всю прелесть строгой типизации. Как же это приятно, когда редактор кода подсказывает тебе какие поля есть в объекте и что ты с ними можешь сделать!
В чем проблема?
Я работаю с CMS Битрикс (хотел тут поставить точку) и приходится иметь дело с массивами разной формы и содержания, запоминать все ключи или искать их в документации. Это занимает драгоценное время.
Решение:
Первым делом на ум приходит использовать PHPDoc, но тут появляется проблема:
Нельзя прописать многомерный массив так, чтобы подсказывал редактор кода. Все найденные мной варианты не работают в VSCode и PhpStorm.
Недолго погуглив я нашёл вариант, который заработал только в VSCode.
В таком случае среда подсказывает какой ключ можно выбрать:
Для PhpStorm есть плагин deep-assoc-completion, он требует описания массива в определённом формате:
В таком случае среда подсказывает ключи:
Остаётся одна проблема, если мы хотим таких подсказок, нам нужно делать подобное описание в каждом файле, где есть наш массив. Особенно актуально для Битрикса.
Моё решение:
Редактор кода хорошо индексирует объекты, так почему бы нам не привести массив к объекту и описать его?! Но как описать объект без класса, ведь PHPDoc в этом тоже помощник?
Мы можем создать класс, в котором опишем все нужные нам поля. И вот наш редактор уже подсказывает нам.
Хорошо, а дальше? Многомерные массивы мы можем описать так же как описываем интерфейсы в TS. Либо выносим вложенный массив в отдельный класс:
Либо прописываем в конструкторе текущего класса:
Обратите внимание, конструктор делаем приватным, чтобы нам не мешался, а вложенному полю обязательно создаём описание. Но PHPStorm его не поймёт.
Резюме
Вариант конечно запарный, но люди, пишущие на TS и так это уже давно делают. Если есть вариант получше, буду рад рассмотреть.
Не хотелось вас оскорблять, но битрикс ненавидят как раз из за таких как вы =))
Как вы делаете в типизацию (которая нивелирует огромное количество ошибок еще на моменте интерпритации) с вашими многомерными массивами?
У битрикса вполне удобоваримый фреймворк со своими сущностями и коллекциями, которые в том числе можно расширять. В данном случае работает поговорка "Можно вывезти девушку из деревни, а вот деревню из девушки не вывести никогда", как будто битрикс заставляет заниматься всей этой ерундой и страдать от этого.
PS. Работаю с битриксом в больших командах на огромных (миллионых по аудитории) проектах, многомерных асоциативных массивов практически не используем и все счастливы, везде классы, контроллеры, модельки, сервисы и прочие радости и где то там глубоко под этими абстракциями запрятано ядро битрикса.
🤣 А я знаю такой анекдот:
Не зли меня, а то заболеешь.
Чем?
Переломом челюсти и сотрясением мозга.
Будьте осторожны так, как первую уже получили в виде дизлайка.
Да чего мне злить, программирование интересная штука и я пришел к выводу, что если ты не можешь покрыть свой код тестами или описать комментариями, то это говнокод =)) Вот человек столкнулся с выдуманной проблемой, если его многомерные массивы разложить на объекты, то помимо того, что любая IDE без всяких костылей начнет его понимать, так появится еще куча полезностей.
Я к тому, что даже в случает автора, он придумал костыль, что надо написать какой то класс, который никогда не будет использоваться по назначению, чтоб потом массив затипизировать в stdObject и просто сделать коммент. Да в таком проекте будет проще застрелиться чем разобраться... зачем делать конструктор приватным и всю эту вакханалию, если можно написать маппер и сделать код вида
class Super {}
$myClasss = new Super($myFackerArray);
и всё, одни плюсы =))
Можно фабрику наваять
$myObject = Factory::create(Super::class, $myFackerArray);
Да и вообще в методологиях программирования уже много всего придумано как делать красиво и легко поддерживаемо, просто как правило битриксоиды не хотят расти, они ставят точку вечно =))
Павел, спасибо за мнение. У меня есть опыт работы с D7 и тоже предпочитаю использовать объекты, но когда имеешь дело со штатными компонентами, то вот там как раз в шаблоне мы и получаем многомерные массивы и это печалит.
А костыль да, ещё тот))
Буду признателен, если поделитесь ссылкой на решение с маппером или фабрикой, которое вы считаете более грамотным.
Ваш код в конкретных случаях будет уникальным, просто почему бы вам ваш пример не сделать проще. Но через конструктор не очень удобно проверять валидность данных, по этому стоит сделать геттеры и сеттеры. Ну и так далее
Согласен про конструктор, немного поправил статью. И я понимаю прекрасно про геттеры и сеттеры, про магические методы я тоже знаю и понимаю. Но нужно будет в конструкторе писать создание экземпляра класса для каждого вложенного объекта.
Вот пример с интерфейсами в TS. Можно вынести объект в отдельный интерфейс и использовать его ещё где-то, а можно оставить объектом с полями. Т.к. такого функционала у php нет, его можно заменить классами и PHPDoc))
Не совсем так (и уж совсем не так про сеттеры).
Вы должны создавать нужные вам для работы сущности. И они совсем не должны повторять структуру массива приходящего от Битрикса. Они могут забирать данные из такого массива (а может и из двух, трех и тп). Равно как из одного массива может собираться несколько совершенно разных сущностей.
Собираться это должно, конечно, в конструкторе. Но так как привязывать конструктор сущности к структуре массива Битрикса не очень хорошо, то конструктор должен описывать структуру сущности (особо в современных версиях ПХП) и вообще может быть приватный. А для создания из массива Битрикса стоит использовать отдельный билдер, чаще это именованный конструктор (для ПХП это будет просто статический метод в классе сущности, что позволит основной конструктор делать приватным при необходимости). Если из массивов получается много объектов - конечно сделать отдельно билдер.
Ну и конечно в сущности лучше избегать сеттеров, придерживаясь правила делать все, что возможно - иммутабельным.
Автор предложил создавать сущности для того чтобы указать их в комментарии.
Остальной поток мыслей ваш вообще не понял если честно =)))
1. Приватный конструктор вам нужен только в единственном случае (по крайней мере в php другого использования я не встречал) это сделать синглтон, чтоб был единый объект для всей системы, но это антипаттерн, в том числе потому что меняет поведение в тестах и может быть изменен из вне.
2. Про структуру сущности и стурктуру массива чет я поплыл, слишком сложно, а уточнее про новые версии php вообще привели в тупик, к чему вы это? =))
3. Что будет с вашим кодом, если туда передать другой массив с другими данными? Вот у вас есть метод, в нем прописан входной параметр array и? вы уверены что в поле ID там будет число в виде заказа с битрикса а не UUID из 1С? Вам просто надо переложить будет валидацию на сторону разработчика, но для этого завезли типизацию и я буду уверен что в setId(int $id) придет именно ID либо код упадет, логгер его подхватит и я увижу сообщение в телеге и пойду исправлять косячную логику.
4. Про избегать сеттеров можно подробнее? Для чего в модели данных избегать иммутабельности? Можно примером, а то прям интересно стало.
Приватный конструктор нужен что бы обозначить рекомендованные способы создания объекта. Считайте это моментом документации.
Полезно так же, когда у объекта может быть несколько наборов обязательных полей. Например если $a == "foo" то обязательно $b, а если $a == "bar" то обязательно $c. В едином конструкторе вам придется описать $a и $b и $c, а проверку обязательности унести в рантайм, что всегда хуже, чем проверки на стадии статического анализа. Создаете два именованных конструктора constructForFoo($b) и сonstructForBar($c) - и сразу все видно в момент написания кода. А основной конструктор изолируете от себя же, что бы в будущем не пойти не тем путем. Если понадобится - приватное всегда можно сделать публичным потом.
Про структуру сущности и стурктуру массива чет я поплыл, слишком сложно, а уточнее про новые версии php вообще привели в тупик, к чему вы это? =))Про версию ПХП - это я про сonstructor property promotion из php 8
Про структуру. Ну вот, условно, у вас из недр битрикса два массива - в одном содержаться данные про автора и посты, в другом - дополнительная информация про автора. Первый массив нужен, что бы создать сущности автора и сущности поста, но первому нужен еще один, второй массив. Те будет Author::constructFromBitrix($bigArray, $additionalArray) и Post::constructFromBitrix($bigArray)
Что будет с вашим кодом, если туда передать другой массив с другими данными?Будет плохо, конечно. Все, что можно сделать - это только проверки в рантайме. По-этому такое вот создание нужно делать сразу и максимально близко к месту получения этих данных. По сути у вас должен быть датамапер, получающий данные из битрикса в его формате и отдающий уже сущности. Правильное наименование конструктора тоже может уберечь от ошибок запихнуть что-то. Главное, что если вы хотите создавать эту сущность из другого места передавая данные в другом формате - не пытаться использовать тот же именованный конструктор, а создать новый именно для этой цели под нужный формат.
но для этого завезли типизацию и я буду уверен что в setId(int $id) придет именно ID либо код упадетГде будет рантайм валидация - это уже не так важно. Никто не мешает вам в именованном конструкторе прописать ассерты Assert::uuid($arry['ID']);
Про избегать сеттеров можно подробнее? Для чего в модели данных избегать иммутабельности?Избегать нужно мутабельности =) Причина простая - иммутабельные объекты предсказуемее. Вы сами частично ответили, когда говорили - почему Синглтон антипаттерн.
Конечно нельзя все делать иммутабельным, но если говорить о сущностях, которые могут изменяться и потом персистится, то "сеттер" тут плохое слово. Потому что должны быть предсказуемые изменения описанные в терминологии предметной области этой сущности. Те самый банальный пример - не setName, а rename. Можно сказать, что это всего лишь как назвать, но на самом деле это о том - как думать об изменении. В целом про сеттеры в моделях написано много всего в интернетах, найти можно.
PS конечно про именованные конструкторы - это один из вариантов. Это может быть отдельный класс билдер, который использует основной публичный конструктор. Или класс билдер создающий объект с помощью рефлексии, что часто используется в различных ORM. Это уже вопросы реализации.
Вы, очень интересный человек с хорошим знанием. Мне, всё это очень интересно. 🙂👍
Бро, а огромные, сколько в секунду нагрузка?)
Огромных, в плане аудитории =)) Если говорить про нагрузку, то на какую то из черных пятниц я видел до 600 RPS доходящих до битрикса. А так все индивидуально, я видел проекты на 1 500 000 RPS, но тот сервис был очень простой на го и складывал просто запросы в сервис очередей.
1. Есть поддержка описательного синтаксиса массивов в PHPStorm https://blog.jetbrains.com/ru/phpstorm/2021/08/phpstorm-2021-2-release/#array-shapes
2. Поздравляю, вы придумали сериализатор, который даже работает и отвечает вашим требованиям, но результат все равно выглядит странно. Предлагаю рассмотреть возможность использовать готовые библиотеки (https://jmsyst.com/libs/serializer) для решения ваших проблем.
1. Это хорошо, что есть поддержка в PHPStorm, не все им пользуются. Но есть проблема, если вы используете массивы часто и много, а особенно если они большие, ваш код будет изобиловать комментариями, описывающими структуру массива. Возьмите штатный компонент битрикса и несколько шаблонов для него и получится, что в каждом шаблоне вам придётся описывать структуру этих массивов, а если там есть result_modifier.php и component_epilog.php, то ещё по разу в каждом файле. Такое себе решение, как по мне.
2. За совет спасибо, изучу.
Не в курсе как оно там в битриксе устроено (и слава богам), но это не проблема, а издержки не зависящие от фреймворка/CMS, если хотите удобно работать с массивами. У вас в любом случае будет либо куча классов описывающих массивы, в которые будет происходить денормализация/десериализация, либо куча аннотаций для методов, порождающих массивы. Классы, очевидно, гибче, самодокументируемые и заведутся везде.
Я не специалист в этой области и скажу честно, что очень заинтересовала статья.
Спасибо автору за работу.
Только без дизлайка.😊
Комментарий недоступен
Почитайте про phpstan или psalm generics и типизацию и не делайте велосипеды.
Спасибо, я что-то читал, не совсем мне подошло, вроде.
Посмотрите это:
https://phpstan.org/writing-php-code/phpdoc-types#local-type-aliases
Я не работал с Битриксом, однако если действительно дело завязано на массивах, то можно с помощью статического анализатора указать типизацию в относительно понятном формате.
Еще есть варианты: DTO,ParameterBag,ValueObject,VeilObject. Они более времязатратные, но структурированные.
Мне всегда было интересно .. молодые "специалисты" по Битрикс читали доки по php?))) Складывается ощущение что Битрикс и php это разные миры.
Уважаемый автор, без обид - изучи доки, паттерны + фраймворки, а не прыгай из node в готовый (пусть и -говно) продукт
А по делу будет что-то или жизни будете учить?))