Как стать автором
Обновить

Комментарии 66

Вы смотрели коллекции в ларавель?
нет конечно, я только по гихабу поиск делал, репозиторий Ларавея в результаты поиска не попал. с другой стороны, мне парсеры на Ларавеле писать?

Спасибо за ссылку.
Посмотрел на коллекции. Весь функционал коллекций это работа с массивом. Но основное назначение моей библиотеки это работа со значением.
Во первых понять определено оно или нет.
Во вторых получит значение строго заданного типа, либо получить значение по умолчанию.
Этот функционал не сложно сочинить самому, занимает он пять строчек кода, но когда тебе это надо сделать для 5 — 15 колонок, тебе очень хочется упихать обработку одной колонки в одну строчку и не страшно если это будет цепочка вызовов, и не страшно что под капотом будет работать не 5, а 500 сторок кода, это всё не имеет значения когда тебе просто надо накидать код, который жить будет от силы неделю, а скорей всего уже завтра утратит свою актуальность.

я не предлагал вам писать парсеры используя ларавель, просто то что вы написали умеет делать (и даже больше) коллекции которые вы могли бы выдрать из ларавель, а по теме ключами массива не могут быть float, они преобразуются в int =)
Про float, верное замечание.

А если использовать генераторы, то и это ограничение можно снять

В PHP7 появились подсказки типов (type hinting)
Всё таки, правильней переводить «type hinting» как «контроль типов». И появился он не в PHP 7, а в PHP 5. В PHP 7 был добавлен контроль скалярных типов и возвращаемых значений. Кроме этого в следующем релизе (7.4) появятся типизированные свойства объектов.
мне удобно работать с базой через PDO, которое отдаёт тебе данные опять же в массивах
А как же PDO::FETCH_OBJ и PDO::FETCH_CLASS?

PDO::FETCH_CLASS — Зачем такие сложности? Специфика задач подразумевает очень короткий срок жизни кода. И даже безотноситено этого, как использование PDO::FETCH_CLASS гарантирует мне соблюдение типов?
Автоматического приведения типов не случиться, присвоения значений по умолчанию тоже.
Писать каждый раз логику? вот я и написал библиотеку один раз что бы использовать везде. Будь у меня источник данных PDO или .csv, или json из какого то API, не важно какой источник данных, если он сводиться к массиву то с помощью своей библиотеки у меня всегда на выходе будут переменные строго заданных типов.
FETCH_COLUM по этой же причине не всегда применим, по этой же причине методу simplify можно отдать массив с нужными нам индексами и он вернёт только заданные колонки, это нужно когда мы из
.csv файла парсим не все колонки, а только две три.

PDO::FETCH_CLASS — Зачем такие сложности? Специфика задач подразумевает очень короткий срок жизни кода. И даже безотноситено этого, как использование PDO::FETCH_CLASS гарантирует мне соблюдение типов?
Не совмем представляю, что такое короткий срок жизни кода. Классы как раз упрощают работу с данными и предоставляют типизацию. Если вы не можете использовать ORM, создавайте простые value-объекты через PDO::FETCH_CLASS или из массивов.

final class User {
  public function getName(): string {
    return $this->name;
  }
}


См. steemit.com/php/@crell/php-use-associative-arrays-basically-never

FETCH_COLUM по этой же причине не всегда применим
array_column

Идея с классами вполне себе альтернативный вариант, но надо гетеры выписать, это чуть сложней чем просто одна строка get('side_numbers')->int().
И в базу писать массив как то проще, с классом придётся этот массив предварительно создать и наполнить.
array_column() — да, спасибо за подсказку, можно использовать внутри simplify().
Короткий срок жизни кода это когда нам надо завести в базу исходные данные которые позже будут обработаны с помощью SQL.
Моя последняя задача была о том что бы из csv залить в базу точки продаж и с помощью описательного адреса для каждой точки определить гео координаты.
А дальше конечные пользователи CRM с точками продаж должны работать через админку Битрикса.
И кругом у тебя массивы — csv — массив, ответ геокодера — массив, ответ ORM Битрикса — массив.
Соответственно с обработкой этих данных и появлялись новые требования к этой библиотеке и исходя из них развивался функционал.

ORM Битрикса же умеет объекты возвращать
Я нашёл два метода:
  1. CIBlockElement::GetPropertyValues
  2. CIBlockElement::GetPropertyValuesArray

Первый возвращает значения с ключом — идентификатором, по которому нельзя понять что внутри, предварительно по ИД надо найти код свойства, то есть предварительно надо словарик свойств получить.
Второй метод возвращает свойства элемента с ключом — кодом, это годный для меня вариант.

Как получить объект?
Очень плохо написано по-русски

Главное что бы код был ок. А пресс релизы пусть пресс секретари пишут и прочие контент менеджеры. Каждоиу своё

НЛО прилетело и опубликовало эту надпись здесь

Последний пример кода на мой вкус очень наглядный.
Изложение конечно скорее развлекательное чем информативное. Но не вижу смысла, мне кажется ни кто не кинется разворачивать либу с помощью композера и тыкать в неё острой палкой, что бы понять на что она способна.
В репозитонии для каждого метода показаны примеры работы, ещё более подробно описано в тестах, было бы желание разобратья.
Вообще конечно статья для получения обратной связи, потому что я конечно доволен как слон, но я всего не знаю и опыт у меня только личный, может быть есть готовые инструменты? Может быть нужен совсем другой подход?
Не знаю. Было бы интересно послушать ответы на эти вопросы.

То есть ты сам для себя написал статью, ок

Кому надо разберётся. Или вообще публиковать ни чего не надо если стиль излржения кому то может показаться не достаточно доходчивым ?

Первое что бросилось в глаза, это метод next() который возвращает генератор. Дальше даже смотреть не стал.

Зачем ArrayHandler->simplify(), когда есть array_column?

Смысл использования ArrayHandlera в том что по get() он выдаёт ValueHandler у которого есть методы для приведения типов.
Можно упрощать массив с помощью array_column(), но для типобезопасности всё равно надо создать экземпляр ArrayHandler.
Смысл использования библиотеки в интеграции функционала.
Можно писать $number = (int)@(vars['key']) всегда на выходе будет int и ни когда не будет ошибки "ключ не найден", но как то не очень нравиться мне такой код.

на мой вкус мой вариант более связный.
Сегодня мой коллега полез в мой код, что бы внести необходимые правки, и сказал что этот мой парсер лажовая фигня.
На что я ему сказал, что да конечно, в простейших случаях можно и без этих украшательств обойтись, но вот если тебе надо из массива взять другой массив, что бы взять другой массив, что бы сравнить значение элемента с таким то индексом со значением элемента из другого массива, то либа вполне себя оправдывает:
$isDiffer = false;
if ($was->has($key) || $after->has($code)) {
    $isDiffer = $after->get($code)->str()
        !== $was->pull($key)->pull()->get('VALUE')->str();
}

/* нативно это примерно так будет :*/
$isDiffer = false;
if (key_exists($key,$was) || key_exists($code,$after)) {
    $isDiffer = (string)($after[$code] ?? "")
        !== (string)(current($was[$key])['VALUE'] ?? "");
}

Вам какой вариант читабельней? Мне первый там всё «по „русски“ написано, можно не знать магию квадратных скобочек и нативных функций, достаточно читать „по русски“.

Почему надо так вышкорябывать данные? потому что это Битрикс и там так своеобразно в обработчик событий подаются „значения до“ и „значения после“. А ты милый разработчик сиди и голову ломай как тебе их сравнить, и не удивляйся что „значения до“ это строки, а соответствующие „значения после“ почему то уже числа.

Тогда уж должно быть так:


$isDiffer = $after->get($code)->str() !== $was->pull($key)->pull()->get('VALUE')->str();

Хотя из кода совершенно не понятно, что это за магическое условие.

Для любителей кастов есть специальная задачка для собеседования:
$a = (float)"77.74" * 100;
$b = (int)$a;

Нужно ответить, чему будет равно $b.

Лакмусовая бумага для выявления тех, кто не понимает, что он творит, занимаясь преобразованием типов.
И что же эта задачка выявит на собеседовании? Что собеседующий — задрот и копается в неэффективностях преобразования типов динамической типизации?
Я бы такого нахрен послал. Если он делает умножение флоатов преобразованных из строки — это клиника.
php-decimal.io
Как раз очень хорошо показывает, есть ли у человека понимание, что скрывается за типом данных, или он просто перегоняет одно в другое, чтобы было удобно / под требования.

В продакшен код, особенно в части взаимодействия по api, практически каждый второй пишет что-то, вроде
return (int)...
...
method((int)$property,...

Или любой другой нужный тип. Так как используемая библиотека требует данных определённого типа. Без понимания, что преобразование типов — это не бесплатная фича динамической типизации. И потеря точности — меньшее из зол. Пример выше как раз о том, что мы таким образом может передать не те данные, что планировали.

Людей, которые сходу говорили, что float (числа с плавающей запятой) — это, вообще-то, диапазон значений, вообще, единицы. Не говоря уже о том, что физический смысл математических операций над float, — это совершенно не то, что многие себе представляют.

Исходя из задачи в начале, можно было обойтись опциями для PDO fetch и json encode/decode, если вопрос касался только скалярных типов.

foreach ($featureMember as $item) {
    $pointInfo = extract($item);    

    $address = (string) ($pointInf['address'] ?? 'Челябинск');
    $longitude = (float) ($pointInfo['longitude'] ?? 61.402554);
    $latitude = (float)  ($pointInfo['latitude'] ?? 55.159897);

    $undefined = !array_key_exists('formatted', $pointInfo);

    $properties = ['longitude' => $longitude, 'latitude' => $latitude, 'address ' => $address ,'undefined'=>$undefined,];
    $result = json_encode($properties);
    output($result);
}

Мне кажется тут можно обойтись без библиотеки

Если это делается один раз — да, можно писать такие процедурки. Если это большой ETL проект — код надо делить на дата провайдеры, трансформеры и сериалайзеры, иначе будет каша.

можно обойтись, а можно не обойтись, здесь:
$pointInf['address']

будет лишний варнинг если индекса нет.
Прелесть библиотеки в автодополнении, жмёшь контрл + пробел и выбираешь вариант, на клавиатуре, за секунду, обычно IDE подставляет первыми вариантами то что тебе надо и для того что бы набить код уходит минимум времени, часто пишешь на одном дыхании.
Можно писать так:
$val1 = (int)(array_key_exists('key1', $vars) ? $vars('key1') : $def1);
$val2 = (int)(array_key_exists('key'2, $vars) ? $vars('key2') : $def1);
$val3 = (float)(array_key_exists('key3', $vars) ? $vars('key3') : $def2);
$val4 = (bool)(array_key_exists('key4', $vars) ? $vars('key4') : $def3);
$val5 = (int)(array_key_exists('key5', $vars) ? $vars('key5') : $def1);

а мне нравиться писать так:
$vars = new ArrayHandler($vars);
$val1 = $vars->get('key1')->default($def1)->int();
$val2 = $vars->get('key2')->default($def1)->int();
$val3 = $vars->get('key3')->default($def2)->double();
$val4 = $vars->get('key4')->default($def3)->bool();
$val5 = $vars->get('key5')->default($def1)->int();

и не надо ни каких ребусов решать с "??" и когда дочитал всю строку выражения не надо вспоминать что результат надо привести к типу который был указан в начале строки.
Всё делается линейно:
$val1 = 
// в $val1 запиши значение следующего выражения
$vars
// из массива $vars
->get('key1')
// возьми элемент с индексом 'key1'
->default($def1)
// используй значение по умолчанию $def1
->int();
// приведи значение к int

Мне такой код легче заходит, библиотека написана для себя и мне не жалко ей поделиться, кто может обойтись без неё обходитесь, кто же вас неволит?

Нормальный этап взросления.
Сначала языковая конструкция кажется ребусом.
Пишется своя реализация, удобная, читаемая.
Потом добавляется в неё сахарок… И где-то начинают крутиться мысли, а что делает мой велосипед такого, что я не использую встроенные в язык конструкции?


?? кинет ворнинг при отсутствии элемента с таким ключом — это фиаско.


Использовать тайп каст, чтобы передавать в функции значения нужных типов — это эпик фейл.


Практически любой сериалайзер сделает всё тоже самое, что и библиотека автора, только скажи в какой объект сложить данные. И к преобразованию типов отнесется со всей серьёзностью, с предсказуемыми ошибками, если данные без потерь не кастятся в целевой тип.

За велосипед с default хочется отправить почитать что-нибудь про Option. Не говоря уже о том, что ValueHandler объединяет в себе и конструктор объекта, и трансформацию, и хранилище. Жуткий класс. Хочется отдать его трем разным людям, чтобы каждый сделал свою часть правильно.

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

И кстати можете дать ссылку на «любой сериалайзер», думаю благодарен за неё буду не я один.
И ссылку на почитать про Option.

С методом default() что не так? обычный сеттер, магия в геттерах для значения:
// значение имеется ? 
$result = $this->has() ?
// да, вернуть значение
 $this->_value
// нет, вернуть значение по умолчанию
: $this->_default;

в чём криминал?

Всё это делается средствами языка. Пример уже приводили. (type) — это каст. ?? — альтернатива, если null.
null ===
gettype / is_ — для получения и проверки типов.


А криминал в том, что для случае null вы, зачем-то, прогоняете альтернативное значение через объект, делая его интерфейс крайне нетривиальным. Например, в каком состоянии будет объект после второго применения default? Можно ли так вообще делать? Вызывая метод default, я откуда-то могу знать, что это объект больше никто не будет использовать? И т.д.

вы, зачем-то, прогоняете альтернативное значение через объект, делая его интерфейс крайне нетривиальным

Куда что я прогоняю? Я устанавливаю свойство экземпляра, это свойство используется при выдаче значения.
Вызывая метод default, я откуда-то могу знать, что это объект больше никто не будет использовать?

А кто код пишет? если вы ссылку на экземпляр отдадите наружу, то с её помощью экземпляром можно будет воспользоваться. Библиотека почему должна этому препятствовать?
Мне вообще не понятно почему библиотека должна как то ограничивать своего пользователя? можно создать экземпляр с каким то значением и получить его приведённым к чему угодно и сколько угодно раз, и каждый раз перед получением значения можно устанавливать разные значения по умолчанию, почему нет?
Если хочется, то с помощью метода type() можно получить исходный тип.
Библиотека предназначена для парсинга, экземпляр класса живёт одну итерацию цикла, в одной ветке условного оператора, но если вам хочется использовать этот класс глобально или гонять по коду туда сюда, то это ваше дело.
Библиотека предназначена для парсинга, экземпляр класса живёт одну итерацию цикла, в одной ветке условного оператора

Вот я и говорю, что у вашего класса довольно нетривиальный интерфейс. Шаг влево, шаг вправо — проведение объекта непредсказуемо.


Это не библиотечный функционал, а хелпер под конкретный, причем типовой, случай.


Т.е. выбранными вами архитектурными решениями, вы неявно резко ограничили применение классов исключительно рамками одной типовой задачи. Это решение может казаться хорошим, пока не захочется переиспользовать данные классы для чего-то другого. И тогда придется решать проблему отсутствия определенных гарантий у получаемых объектов, и весь код сразу станет не таким уж и классным.

да классная мощная вещь, мой вариант сильно попроще.
extract 1) небезопасно, 2) медленно
Писать проверки вручную утомительно
Лучше посмотрите в сторону статического анализа кода.
имелось в виду бесконечные:
key_exists('key',$array);
!empty(value) ? value : $default_val;
Такие проверки могут понадобиться лишь на стыке слоёв, например, для валидации входящих данных. В этом случае существует множество готовых валидаторов. Если речь о работе с БД, то приведения типов вполне достаточно, так как с БД «жесткий» контракт.
Типобезопасности в вашем решении я не вижу. Аналогов вашей утилиты нет потому, что непонятно, какую задачу вы решаете.
Кстати, Psalm поддерживает ассоциативные массивы psalm.dev/docs/annotating_code/type_syntax/array_types/#object-like-arrays — вот с ним можно добиться кое-какой типобезопасности.
Получить с фронта JSON, распарсить и положить в базу в причёсанном виде.
Передать в класс-фабрику значения правильных типов.
Вот это две моих боли которые привели к оформлению кода в библиотеку.

Сначала класс назвался ArrayParser и всё что мог это выдавать значения по индексу.
Меня утомляло выписывать однообразные проверки, тут в коментах уже приводил примеры, вообще использование однообразного кода раздражает, если что то делается одинаково оно должно быть упаковано в метод.

Потом мне надо было получать в API данные и тупо фигачить их в базу, но не в плоскую структуру а в иерархическую, перед тем как значение положить в базу его надо было привести из строки в какой то тип, потом я стал формировать записи БД через методы классов и уже в методы класса-фабрики надо было передавать те же самые значения, и когда ты в аргумент с типом инт отдаёшь переменную с типом стринг, интерпритатор почему то падает.
И вот как то так пришёл к приведению типов.
При чём вся эта крутоверть была не на одном проекте а на четырёх разных за полгода, с разными стеками, общее было только в PHP, даже базы были разные, SQLite Postgres MySql, полный привет, особенно что касается работ с датами, везде свой методы.

Я копипастил класс из проекта в проект, потом сделал первую версию библиотеки, оказалось что устанавливать через композер это очень удобно.
Потом думал о том что ещё можно добавить в класс, и конечно использовал класс в работе, чего не хватало или что элементарного можно было добавить — добавил.
Последний месяц уже специально для публикации готовил код. По репозиторию можно понять.
Да, конечно подход, «всё есть строка» это конечно правильный подход, но не для обработки данных.
Вообще это всё прелюдия к другой очень большой публикации, про работу с данными, ближе к новому году или сразу после него код дойдёт до нужной кондиции и сделаю релиз.

Вышёл в свет с этой библиотечкой, получил массу советов.
Всем спасибо, попробую использовать ваши советы в своей повседневной работе.
Фрактал вроде как раз эти задачи и решает.
fractal.thephpleague.com
!empty(value)? value: $default_val;
empty() не подходит здесь, потому что для integer, 0 не пустое значение. Тоже самое для false для boolean.
Проверяйте на null.
$array['key'] ?? $default_value;

Посмотрим в код:
D:\project>php -a
Interactive shell

php > echo empty(0) ? 'true' : 'false';
true
php > echo empty('') ? 'true' : 'false';
true
php > echo empty(true) ? 'true' : 'false';
false
php > echo empty(false) ? 'true' : 'false';
true
php > exit

0, '' (пустая строка) — значения которые надо заменить на значения по умолчанию, в моём понимании, и null само собой, и пустой массив. Я когда использую empty() я знаю что я делаю.
В самой библиотеки вообще смотрим не на результат от empty($value), а смотрим на ValueHandler::has(), если мы берём с помощью ArrayHandler::get() из массива элемент с несуществующим индексом, то перед приведением типа мы используем значение по умолчанию.
Код
!empty(value)
, был приведён в качестве примера.
Критики в адрес вашего способа решения проблемы и так много, так что просто хочу вам посоветовать посмотреть в сторону таких шаблонов проектирования, как Value Object Data Transfer Object. Это не разной степени универсальности библиотека, а подход, который нужно будет реализовывать раз за разом, но в итоге у вас всегда будут именно те объекты именно с теми умениями, которые вам нужны. Еще можете посмотреть в сторону Enum.

При этом это не замена ORM и чего угодно еще, а просто способ работы с данными, который позволяет почти на 100% решить проблемы с документацией к данным в коде, и конечно собственно их использованием.
Спасибо, про DTO знаю. Сама библиотека родилась из задачи формирования DTO для фабрики классов.
То есть данная библиотека помогает внутри DTO разбирать массив на части и раскладывать его по свойствам DTO, или же она подготавливает массив для передачи его в DTO?
Данная библиотека помогает мне распарсить ответ API или какого либо метода, когда такой ответ представляет собой массив (обычно с вложенными массивами).
С DTO я редко заморачиваюсь, а если заморачиваюсь, то задаются свойства DTO через сеттеры (делаем прототип), потом это DTO отдаётся в конструктор и получаем наконец DTO без сеттеров, то есть изменить состояние не получиться даже если очень захочется.
И конечно само DTO из методов имеет только геттеры, вся бизнес логика в других классах.
Передачу параметров через массивы не практикую, потому что это сводит на нет статический анализ, я использую или DTO, или строго заданные типы параметров в методах.

Коллега правильно показывает направление.
Посмотрите https://github.com/spatie/data-transfer-object — конвертация массивов в DTO со строго типированными properties. Совмещенные с коллекциями https://github.com/tightenco/collect — получите готовый не-велосипед.

Ну у топикстартера основная цель — автоматизация type checking. В этом пакете я ее не увидел.

Коллега молодец, и вам спасибо за ссылки.
Но не всегда такие линейные переливания из пустого в порожнее.
Конечно для каких то случаев отличное решение.

Вам пожалуйста и тоже спасибо, что потрудились выложить интересную работу.


Но не всегда такие линейные переливания из пустого в порожнее.

И, кстати, если переливания не линейные, т.е. нужно что-то преобразовать во время переливания массива в DTO, то, скорее всего, на эти преобразования нужно сделать отдельный объект (класс), чтобы соблюсти Single Responsibility Principle — data transformer.


Вы посмотрите повнимательней DTO-пакет, может быть там можно через dependency injection впрыснуть свой кастомный data transformer. Или пакет форкнуть и доработать под себя.


Если нет, я бы все равно порекомендовал подумать, как вынести трансформацию в отдельный объект и проводить ее до переливания из массива в DTO (или после?).


Суть такого рефакторинга — сделать так, чтобы интеллектуальный DTO занимался только проверкой типов и, естественно, переносом данных в себе (он же data transfer object по основному назначению).


Кстати, еще по вашей задаче:


  • PHP 7.4 Typed properties — уже день, как вышел, скоро на всех виртуальных хостингах будет, а на VPS вы сами можете поставить.
  • PHP Static Analysis Tool очень полезная штука сама по себе.
спасибо
спасибо
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории