60 дней фильмов
и сериалов
18+
Условия подписки Плюс
Мульти: clck.ru/YMaCq
VC60
Забрать

Как в PHP из массивов сделать объекты с подсказками?

Когда надоело запоминать ключи массивов и хочется пользоваться подсказками любимого редактора кода на помощь приходит PHPDoc и немного смекалки.

Недавно я очень близко познакомился с TypeScript и познал всю прелесть строгой типизации. Как же это приятно, когда редактор кода подсказывает тебе какие поля есть в объекте и что ты с ними можешь сделать!

В чем проблема?

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

Решение:

Первым делом на ум приходит использовать PHPDoc, но тут появляется проблема:

/** * int[] * array * array<string> */

Нельзя прописать многомерный массив так, чтобы подсказывал редактор кода. Все найденные мной варианты не работают в VSCode и PhpStorm.

Недолго погуглив я нашёл вариант, который заработал только в VSCode.

/** * @var array $arr $arr * @var array $arr['fields'] * @var array $arr['fields']['fieldName'] * @var array $arr['fields']['fieldName']['name'] * @var array $arr['fields']['fieldName']['model'] * @var array $arr['fields'][fieldName]['width'] * @var array $arr['fields'][fieldName]['align'] * @var array $arr['fields'][fieldName]['format'] * @var array $arr['fields'][fieldName]['title'] * @var array $arr['fields'][fieldName]['desc'] * @var array $arr['fields'][fieldName]['readonly'] * @var array $arr['fields'][fieldName]['type'] * @var array $arr['fields'][fieldName]['options'] * @var array $arr['fields'][fieldName]['editor'] * @var array $arr['fields'][fieldName]['default'] **/ $arr = [ 'fields' => [ 'fieldName' => [] ] ];

В таком случае среда подсказывает какой ключ можно выбрать:

Для PhpStorm есть плагин deep-assoc-completion, он требует описания массива в определённом формате:

/** * @var array $arr = [ * 'fields' => [ * $anyKey => [ * 'name' => 'sale', * 'model' => string, * 'width' => '100px', * 'align' => 'center', * 'format' => 'nice', * 'title' => 'Sale', * 'desc' => 'A deal another person that results in money', * 'readonly' => false, * 'type' => 'password', * 'options' => ['option1', 'option2'], * 'editor' => false, * 'default' => '', * ], * ], * ] */ $arr=[ 'fields' => [ '0' => [ 'model' => 2 ] ] ];

В таком случае среда подсказывает ключи:

Остаётся одна проблема, если мы хотим таких подсказок, нам нужно делать подобное описание в каждом файле, где есть наш массив. Особенно актуально для Битрикса.

Моё решение:

Редактор кода хорошо индексирует объекты, так почему бы нам не привести массив к объекту и описать его?! Но как описать объект без класса, ведь PHPDoc в этом тоже помощник?

Мы можем создать класс, в котором опишем все нужные нам поля. И вот наш редактор уже подсказывает нам.

Хорошо, а дальше? Многомерные массивы мы можем описать так же как описываем интерфейсы в TS. Либо выносим вложенный массив в отдельный класс:

Либо прописываем в конструкторе текущего класса:

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

Резюме

Вариант конечно запарный, но люди, пишущие на TS и так это уже давно делают. Если есть вариант получше, буду рад рассмотреть.

!function(e){var o={};function t(n){if(o[n])return o[n].exports;var r=o[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,t),r.l=!0,r.exports}t.m=e,t.c=o,t.d=function(e,o,n){t.o(e,o)||Object.defineProperty(e,o,{enumerable:!0,get:n})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,o){if(1&o&&(e=t(e)),8&o)return e;if(4&o&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(t.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&o&&"string"!=typeof e)for(var r in e)t.d(n,r,function(o){return e[o]}.bind(null,r));return n},t.n=function(e){var o=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(o,"a",o),o},t.o=function(e,o){return Object.prototype.hasOwnProperty.call(e,o)},t.p="",t(t.s=0)}([function(e,o,t){"use strict";t.r(o);const n=e=>{if("object"==typeof Air){Air.import("module.ajaxify").one("Before page changed",()=>{e&&e()})}};((e="teaser",o=[],t="vc")=>{const r={root:e,index:e+"--index",entry:e+"--entry",loaded:e+"--loaded",location:e+"--%location%",sitename:`${e}--${window.__codename||t}`},a=document.querySelector("."+r.root),i=document.querySelector('[air-module="module.feed"]');if(a){a.classList.add(r.sitename),-1===r.location.indexOf("location")&&a.classList.add(r.location),i?a.classList.add(r.index):a.classList.add(r.entry);const e=()=>{a.classList.add(r.loaded)};return new Promise(t=>{var i;(i=o,Promise.all(i.map((function(e){return new Promise((function(o){var t=document.createElement("img");t.onload=()=>{o(t)},t.onerror=o,t.src=e}))})))).then(()=>{t({showBanner:e,oneBeforePageChange:n,rootHTML:a,css:r,siteName:window.__codename})})})}})("kpsk-footer",["https://leonardo.osnova.io/db4d29e8-6b05-57c9-a668-8be251b5999f/","https://leonardo.osnova.io/4bc540c7-94c3-523d-a568-289bb3048c90/","https://leonardo.osnova.io/f9b0fdc7-0122-5954-86d2-a9c7b69464e5/","https://leonardo.osnova.io/b955990b-dbc0-5bf5-b6b4-d580e1ae8174/"]).then(e=>{e.showBanner()})}]);
0
23 комментария
Популярные
По порядку
Написать комментарий...

Не хотелось вас оскорблять, но битрикс ненавидят как раз из за таких как вы =))
Как вы делаете в типизацию (которая нивелирует огромное количество ошибок еще на моменте интерпритации) с вашими многомерными массивами?

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

PS. Работаю с битриксом в больших командах на огромных (миллионых по аудитории) проектах, многомерных асоциативных массивов практически не используем и все счастливы, везде классы, контроллеры, модельки, сервисы и прочие радости и где то там глубоко под этими абстракциями запрятано ядро битрикса.

8

🤣 А я знаю такой анекдот:
Не зли меня, а то заболеешь.
Чем?
Переломом челюсти и сотрясением мозга.
Будьте осторожны так, как первую уже получили в виде дизлайка.

0

Да чего мне злить, программирование интересная штука и я пришел к выводу, что если ты не можешь покрыть свой код тестами или описать комментариями, то это говнокод =)) Вот человек столкнулся с выдуманной проблемой, если его многомерные массивы разложить на объекты, то помимо того, что любая IDE без всяких костылей начнет его понимать, так появится еще куча полезностей.

Я к тому, что даже в случает автора, он придумал костыль, что надо написать какой то класс, который никогда не будет использоваться по назначению, чтоб потом массив затипизировать в stdObject и просто сделать коммент. Да в таком проекте будет проще застрелиться чем разобраться... зачем делать конструктор приватным и всю эту вакханалию, если можно написать маппер и сделать код вида

class Super {}
$myClasss = new Super($myFackerArray);

и всё, одни плюсы =))

Можно фабрику наваять
$myObject = Factory::create(Super::class, $myFackerArray);

Да и вообще в методологиях программирования уже много всего придумано как делать красиво и легко поддерживаемо, просто как правило битриксоиды не хотят расти, они ставят точку вечно =))

7

Павел, спасибо за мнение. У меня есть опыт работы с D7 и тоже предпочитаю использовать объекты, но когда имеешь дело со штатными компонентами, то вот там как раз в шаблоне мы и получаем многомерные массивы и это печалит.
А костыль да, ещё тот))
Буду признателен, если поделитесь ссылкой на решение с маппером или фабрикой, которое вы считаете более грамотным.

0

Ваш код в конкретных случаях будет уникальным, просто почему бы вам ваш пример не сделать проще. Но через конструктор не очень удобно проверять валидность данных, по этому стоит сделать геттеры и сеттеры. Ну и так далее

0

Согласен про конструктор, немного поправил статью. И я понимаю прекрасно про геттеры и сеттеры, про магические методы я тоже знаю и понимаю. Но нужно будет в конструкторе писать создание экземпляра класса для каждого вложенного объекта.
Вот пример с интерфейсами в TS. Можно вынести объект в отдельный интерфейс и использовать его ещё где-то, а можно оставить объектом с полями. Т.к. такого функционала у php нет, его можно заменить классами и PHPDoc))

0

Не совсем так (и уж совсем не так про сеттеры).
Вы должны создавать нужные вам для работы сущности. И они совсем не должны повторять структуру массива приходящего от Битрикса. Они могут забирать данные из такого массива (а может и из двух, трех и тп). Равно как из одного массива может собираться несколько совершенно разных сущностей.

Собираться это должно, конечно, в конструкторе. Но так как привязывать конструктор сущности к структуре массива Битрикса не очень хорошо, то конструктор должен описывать структуру сущности (особо в современных версиях ПХП) и вообще может быть приватный. А для создания из массива Битрикса стоит использовать отдельный билдер, чаще это именованный конструктор (для ПХП это будет просто статический метод в классе сущности, что позволит основной конструктор делать приватным при необходимости). Если из массивов получается много объектов - конечно сделать отдельно билдер.

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

0

Автор предложил создавать сущности для того чтобы указать их в комментарии.
Остальной поток мыслей ваш вообще не понял если честно =)))

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

2. Про структуру сущности и стурктуру массива чет я поплыл, слишком сложно, а уточнее про новые версии php вообще привели в тупик, к чему вы это? =))

3. Что будет с вашим кодом, если туда передать другой массив с другими данными? Вот у вас есть метод, в нем прописан входной параметр array и? вы уверены что в поле ID там будет число в виде заказа с битрикса а не UUID из 1С? Вам просто надо переложить будет валидацию на сторону разработчика, но для этого завезли типизацию и я буду уверен что в setId(int $id) придет именно ID либо код упадет, логгер его подхватит и я увижу сообщение в телеге и пойду исправлять косячную логику.

4. Про избегать сеттеров можно подробнее? Для чего в модели данных избегать иммутабельности? Можно примером, а то прям интересно стало.

1

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

Приватный конструктор нужен что бы обозначить рекомендованные способы создания объекта. Считайте это моментом документации.

Полезно так же, когда у объекта может быть несколько наборов обязательных полей. Например если $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. Можно сказать, что это всего лишь как назвать, но на самом деле это о том - как думать об изменении. В целом про сеттеры в моделях написано много всего в интернетах, найти можно.

0

PS конечно про именованные конструкторы - это один из вариантов. Это может быть отдельный класс билдер, который использует основной публичный конструктор. Или класс билдер создающий объект с помощью рефлексии, что часто используется в различных ORM. Это уже вопросы реализации.

0

Вы, очень интересный человек с хорошим знанием. Мне, всё это очень интересно. 🙂👍

0

Бро, а огромные, сколько в секунду нагрузка?)

0

Огромных, в плане аудитории =)) Если говорить про нагрузку, то на какую то из черных пятниц я видел до 600 RPS доходящих до битрикса. А так все индивидуально, я видел проекты на 1 500 000 RPS, но тот сервис был очень простой на го и складывал просто запросы в сервис очередей.

0

1. Есть поддержка описательного синтаксиса массивов в PHPStorm https://blog.jetbrains.com/ru/phpstorm/2021/08/phpstorm-2021-2-release/#array-shapes
2. Поздравляю, вы придумали сериализатор, который даже работает и отвечает вашим требованиям, но результат все равно выглядит странно. Предлагаю рассмотреть возможность использовать готовые библиотеки (https://jmsyst.com/libs/serializer) для решения ваших проблем.

2

1. Это хорошо, что есть поддержка в PHPStorm, не все им пользуются. Но есть проблема, если вы используете массивы часто и много, а особенно если они большие, ваш код будет изобиловать комментариями, описывающими структуру массива. Возьмите штатный компонент битрикса и несколько шаблонов для него и получится, что в каждом шаблоне вам придётся описывать структуру этих массивов, а если там есть result_modifier.php и component_epilog.php, то ещё по разу в каждом файле. Такое себе решение, как по мне.
2. За совет спасибо, изучу.

0

Не в курсе как оно там в битриксе устроено (и слава богам), но это не проблема, а издержки не зависящие от фреймворка/CMS, если хотите удобно работать с массивами. У вас в любом случае будет либо куча классов описывающих массивы, в которые будет происходить денормализация/десериализация, либо куча аннотаций для методов, порождающих массивы. Классы, очевидно, гибче, самодокументируемые и заведутся везде.

2

Я не специалист в этой области и скажу честно, что очень заинтересовала статья.
Спасибо автору за работу.
Только без дизлайка.😊

2

Я думал автор ArrayAccess переизобрел, а тут оно вона как...

1

Почитайте про phpstan или psalm generics и типизацию и не делайте велосипеды.

0

Спасибо, я что-то читал, не совсем мне подошло, вроде.

0

Посмотрите это:
https://phpstan.org/writing-php-code/phpdoc-types#local-type-aliases
Я не работал с Битриксом, однако если действительно дело завязано на массивах, то можно с помощью статического анализатора указать типизацию в относительно понятном формате.
Еще есть варианты: DTO,ParameterBag,ValueObject,VeilObject. Они более времязатратные, но структурированные.

0

Мне всегда было интересно .. молодые "специалисты" по Битрикс читали доки по php?))) Складывается ощущение что Битрикс и php это разные миры.
Уважаемый автор, без обид - изучи доки, паттерны + фраймворки, а не прыгай из node в готовый (пусть и -говно) продукт

–1

А по делу будет что-то или жизни будете учить?))

0
Читать все 23 комментария
Delivery Club запустит для курьеров-студентов стипендию в 5000 рублей за сессию без троек Статьи редакции

Программа рассчитана на 300 курьеров из регионов.

Откуда берут взрослые деревья для парков и улиц

А также сколько они стоят и почему выращивать их — неплохой бизнес.

Продавец eBay из Кургана стала победителем в финале Всероссийского конкурса «Молодой предприниматель России 2021»

27 ноября в Москве состоялся финал ежегодного конкурса «Молодой предприниматель России 2021». В нём приняли участие предприниматели и самозанятые в возрасте до 35 лет. Всего было подано более 300 заявок из 43 регионов страны.

ЦБ определит фиксированную сумму для возврата после кражи денег мошенниками Статьи редакции

За квартал банки вернули жертвам около 8% от украденных денег.

PlayStation от Sony Group запускает сервис ежемесячной подписки

Sony Group планирует создать новую услугу подписки, чтобы конкурировать с популярным сервисом Xbox от компании Microsoft, пишет Bloomberg.

Как Embacy создает дизайн для клиентов из XXIV стран. Часть I: Привлечение Клиентов

Всем привет! Я — Вадим Орлов, основатель Embacy. Мы создаем брендинг, сайты и иногда интерфейсы для digital-компаний со всего мира — от Австралии до Калифорнии и от Норвегии до ЮАР. А это — первая статья из цикла о том, как у нас все внутри работает. Статья, как и все в цикле, будет длинная, присаживайтесь поудобнее. Если вы на встрече или…

Инвестиции «в одно касание»

Производитель интерактивного оборудования делится секретами успеха и рассказывает, какие конкурентные преимущества дает статус резидента ОЭЗ «Технополис Москва».

VC превратился в книгу жалоб

Вам не кажется, что VC превращается в сайт для жалоб на разные сервисы и компании?

OPS-инструкция для стартапа по доставке: как выжить и наладить операционку

В преддверии первой годовщины запуска сервиса гиперлокальной доставки Прямиком, который развивает наша компания, вспомнили с ребятами из операционного отдела, как всё начиналось. Андрей Резник, региональный менеджер Master Delivery, и Анатолий Голованец, старший менеджер по франчайзингу, собрали ТОП-5 советов для тех, кто сейчас в самом начале.

Wildberries запустил партнёрские сортировочные центры — партнёры смогут заработать на обработке чужих заказов Статьи редакции

Ритейлер рассчитывает, что программа ускорит обработку и доставку товаров.

«Сбер» представил «умную» колонку SberBox Time Статьи редакции

Стоит 7990 рублей.

«Сбер»
null