Всем привет, прошёл почти год с публикации первой части. Обсуждение в комментариях было жарким, выводы я для себя сделал, изменения в либу внёс почти сразу, но написать об этом времени не нашлось.
На днях расширил функционал парой методов, и хочу поделиться с вами этими новостями.
И конечно я напишу о работе над ошибками.
Для тех кто не знал и забыл, что такое ArrayHandler
Spoiler
Ответим на вопрос: "что такое типобезопасная работа с массивами в PHP ?"
Типобезопасная это:
Когда мы можем получить элемент массива без опасности получить эксепшен о не существующем индексе;
Когда мы можем передать полученный элемент в метод и мы точно не получим эксепшен несоответствия типов;
И последние в списке, но не последнее по значению, типобезопасный код - это когда нам приятно писать хороший код. Для меня это наверное даже самое важное, потому что я устал от бесконечных:
$a = 0;
if (key_exists($key, $collection))
{
$a = (int) $collection[$key];
}
И даже когда я узнал о современном способе писать через:
$a = (int) $collection[$key] ?? 0;
Мне от этого не полегчало, потому что вложенность у массивов бывает "мама не горюй".
Кроме этих трёх преимуществ, ArrayHandler даёт иммутабельность, то есть мы можем спокойно пробрасывать наш ArrayHandler, через слои абстракций и ни кто случайно не поменяет элементы внутри исходного массива. Для того что бы поменять значение, надо создать новый экземпляр ArrayHandler - и такое пропустить на код ревью сложнее, чем пропустить запись нового значения в элемент массива.
Не буду копипастить примеры работы с либой, их можно посмотреть в первой части или можно почитать документацию.
Либа устанавливается через Композер:
composer require sbwerewolf/language-specific
Есть версии для PHP 5.6 / 7.0 / 7.2 .
Это было долгое вступление теперь по существу.
Обновления
Пару дней назад мне было и грустно и скучно, захотелось сделать что ни будь хорошее, например сделать так что бы, когда пробегаешь по элементам с помощью foreach(), можно было не только получить элемент ( ValueHandler ), но индекс этого элемента.
Я с энтузиазмом принялся за работу и уже написав тонны кода, наткнулся на комент в документации к PHP, который сделал весь новый код бесполезным.
Оказывается можно делать так:
yield $key => $value;
И в foreach() будет возвращён индекс элемента. Эврика!
Теперь IArrayHandler::pulling() возвращает и новый IArrayHandler от элемента массива, и индекс этого элемента. Я был счастлив, кажется теперь ArrayHandler стал идеальной библиотекой для работы с массивами (в том виде как я обозначил в начале статьи).
На эмоциях я запилил ещё два метода. То есть ещё один метод - IArrayHandler::getting(), плюс я добавил поддержку интерфейса Iterator и теперь экземпляр ArrayHandler можно использовать в foreach() как обычный массив.
Теперь IArrayHandler::pulling() возвращает ArrayHandler для каждого вложенного массива (будут проигнорированы элементы исходного массива, которые не являются массивами). Название метода "pulling" образовалось от названия другого метода - IArrayHandler::pull(), с помощью которого можно получить экземпляр ArrayHandler от элемента массива.
IArrayHandler::getting() возвращает IValueHandler для всех элементов массива, не являющимися массивами. Название метода "getting" образовалось от названия другого метода - IArrayHandler::get(), с помощью которого можно получить экземпляр IValueHandler от элемента массива.
Теперь у нас IArrayHandler::pulling() для массивов, и IArrayHandler::getting() для всех остальных типов.
Более наглядно:
$data = new ArrayHandler(
[
'first' => ['A' => 1],
'next' => ['B'=>2],
'last' => ['C'=>3],
4=>[5,6],
7,
8,
9
]);
echo 'arrays'.PHP_EOL;
foreach ($data->pulling() as $key => $value) {
echo "[$key] => class is ".get_class($value).' '.PHP_EOL;
}
echo 'values'.PHP_EOL;
foreach ($data->getting() as $key => $value) {
echo "[$key] => {$value->asIs()} , class is ".get_class($value).' '.PHP_EOL;
}
Вывод скрипта:
arrays
[first] => class is LanguageSpecific\ArrayHandler
[next] => class is LanguageSpecific\ArrayHandler
[last] => class is LanguageSpecific\ArrayHandler
[4] => class is LanguageSpecific\ArrayHandler
values
[5] => 7 , class is LanguageSpecific\ValueHandler
[6] => 8 , class is LanguageSpecific\ValueHandler
[7] => 9 , class is LanguageSpecific\ValueHandler
Если нам надо пробежаться по всем элементам исходного массива, мы используем обычный foreach():
$data = new ArrayHandler(
[
'first' => ['A' => 1],
'next' => ['B'=>2],
'last' => ['C'=>3],
4=>[5,6],
7,
8,
9
]);
echo 'ALL'.PHP_EOL;
foreach ($data as $key => $value) {
$type = gettype($value->asIs());
echo "[$key] => value type is $type , class is ".get_class($value).PHP_EOL;
}
Вывод скрипта:
ALL
[first] => value type is array , class is LanguageSpecific\ValueHandler
[next] => value type is array , class is LanguageSpecific\ValueHandler
[last] => value type is array , class is LanguageSpecific\ValueHandler
[4] => value type is array , class is LanguageSpecific\ValueHandler
[5] => value type is integer , class is LanguageSpecific\ValueHandler
[6] => value type is integer , class is LanguageSpecific\ValueHandler
[7] => value type is integer , class is LanguageSpecific\ValueHandler
Соответственно, если мы какие то элементы хотим обработать как элементы, а какие то как массивы, то мы в foreach(), делаем так:
foreach ($data as $key => $value) {
/* @var \LanguageSpecific\ValueHandler $value */
if($value->type() === 'array'){
$handler = new ArrayHandler($value->array());
/* some code */
}
}
Работа над ошибками
Метод IValueHandler::default() перестал быть статическим, его опасность до меня пытался донести @GreedyIvan, до меня дошло через неделю, спасибо.
Метод ArrayHandler::simplify() был удалён, потому что на самом деле
Зачем ArrayHandler->simplify(), когда есть array_column? (c) @olegmar
Cпасибо @olegmar.
Метод IArrayHandler::next() был заменён на IArrayHandler::pulling(), этот метод перебирает все вложенные массивы (первый уровень вложенности). Не то что бы комент от @Hett меня прямо убедил, но к размышлениям подтолкнул.
Спасибо @ReDev1L за поддержку в коментах.
Был добавлен метод IArrayHandler::raw(), что бы можно было получить исходный массив. Раньше, когда не было возможности получить индекс элемента, приходилось перебирать исходный массив, сейчас по опыту использования, бывает необходимость добавить/убавить элементы массива и создать из изменённого массива новый ArrayHandler.
На этом всё. Спасибо за чтение.
Post scriptum
Добавил поддержку JsonSerializable, теперь получить JSON стало проще:
$data = new ArrayHandler(
[
'first' => ['A' => 1],
'next' => ['B'=>2],
'last' => ['C'=>3],
4=>[5,6],
7,
8,
9
]);
echo json_encode($data);
Вывод:
#raw
{"first":{"A":1},"next":{"B":2},"last":{"C":3},"4":[5,6],"5":7,"6":8,"7":9}
#formatted
{
"first": {
"A": 1
},
"next": {
"B": 2
},
"last": {
"C": 3
},
"4": [
5,
6
],
"5": 7,
"6": 8,
"7": 9
}
skyeff
Отсюда вывод: не используйте массивы, используйте DTO и сериализацию. И не надо рассказывать про то что сериализация — это медленно, а DTO — сложно в проектировании. Зато в поддержке получится дешевле на порядок, а разницу можно вложить в железо, если вам вдруг сериализация так сильно на скорость повлияла.
SbWereWolf Автор
я не могу требовать от Яндекса что бы они выдавали мне объекты, бесполезно ждать от fgetcsv() что бы мне выдавались объекты с конкретным набором свойств.
Мне как раз и нужна эта «безопасная» работа с массивами что бы спокойной конструировать DTO, или как то по другому парсить ответы от API и делать другие подобные вещи.
DarthRaven
Но вы можете превратить json от яндекса в объект самостоятельно, чем-то вроде https://jmsyst.com/libs/serializer
SbWereWolf Автор
Классная наверное штука, только узнал я про неё поздно :)
Вопрос конечно что делать если какое то свойство было, а потом его не стало, это не про Яндекс конкретно, а вообще такое бывает, что иногда один набор параметров в ответе, иногда другой.
И как бы из CSV делать объекты, если у CSV структура вариативная, у одного файла такие колонки, у другого чуть чуть другие.
Обращаешься к свойству, а его нет, и? какой то аналог key_exists() придётся использовать.
В общем согласен, есть альтернативы, но я про них, во первых, не знаю, во вторых, я с ними работать не умею.
Конечно надо делиться альтернативами. У любой статьи самое интересное в коментах :)
BoShurik
При любом подходе это будет проблемой.В общем случае свойство будет инициализировано
null
Если говорить о symfony/serializer, то это решается кастомным нормалайзером
Для одного файла один объект, для другого — другой.