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 комментария
«Бизнесом чуть-чуть занимаюсь, чтобы не закиснуть мозгами»: Олег Тиньков рассказал о продолжении борьбы с лейкемией Статьи редакции

Бизнесмен считает, что победить болезнь окончательно невозможно.

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

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

Хоум кредит про меня забыли. Долг с 2009года

Решил я значит проверить свою кредитную историю на одном из известных ресурсах. И что я обнаружил? Действующий долг в #хоум_кредит с 2009 года и как следствие краснющую историю! Я конечно же позвонил в эту контору, говорю каким образом банк забыл про меня? - а вы знаете? Мы вам звонили звонили... но не дозвонились ... Так же как и вашим…

«Год пандемии — один из лучших для бизнеса». Основательница Wanna?Be! Ирина Стройнова, — о 10 годах продаж украшений

Наценка на ювелирные украшения за 10 лет снизилась с 700 до 300%, производители и дизайнеры драгоценностей стали зарабатывать гораздо скромнее. Поднимать цены нельзя — мировой коронавирусный кризис разоряет покупателей. К тому же, с 2022 года в России появится маркировка ювелирных изделий, что тоже увеличит нагрузку на бизнес. По просьбе Lamoda,…

Основательница бренда Wanna?Be! Ирина Стройнова
Оружейный барон в Ecommerce: c нуля до 1,3 млн рублей выручки в месяц c контекстной рекламы

Как делать архитектуру рекламных кампаний под автостратегии, чтобы те принесли +300 т.р.? Как переориентирование стратегии с ДРР на валовую прибыль дало буст в 2 раза? Рассказываем, как поэтапно пришли к максимальной выручке из контекста за всю историю интернет-магазина и вышли в плюс уже в первый месяц.

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

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

И сотрудников тоже касается: кибербуллинг на рабочем месте
Design vector created by pikisuperstar - www.freepik.com
Инвестиции «в одно касание»

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

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

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

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

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

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

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

null