Всем привет! Расскажу о большом обновлении в пакете sbwerewolf/language-specific.

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

Допустим, вы по API получили JSON с большой вложенностью, и вам нужно достать из JSON какое-то значение, которое зарыто поглубже. Что делать ? конечно преобразовать JSON строку в ассоциативный массив, а дальше что делать ? Конечно прописать все индексы до искомого элемента, получится что то такое:

$formatted = (string)$response["response"]["GeoObjectCollection"]["featureMember"]
  [0]["GeoObject"]["metaDataProperty"]["GeocoderMetaData"]["Address"]["formatted"]
  ?? "Адрес не найден";

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

Пакет sbwerewolf/language-specific позволяет избавить код от сплошного перечисления индексов, замороченных выражений, скобочек и операторов ??.

С ним можно будет написать так:

$data = (new AdvancedArrayFactory())->makeAdvancedArray($response);
$formatted = $data
    ->pull("response")
    ->pull("GeoObjectCollection")
    ->pull("featureMember")
    ->pull()
    ->pull("GeoObject")
    ->pull("metaDataProperty")
    ->pull("GeocoderMetaData")
    ->pull("Address")
    ->get("formatted")
    ->default("Адрес не найден")
    ->str();
// 1, Mohammed Bin Rashid Boulevard, Downtown Dubai, Dubai, United Arab Emirates

И никаких забот о том, что по пути какого-то элемента может не быть, или элемент "formatted" может внезапно оказаться с типом bool вместо string.

Назначение пакета sbwerewolf/language-specific

Суть пакета в том, чтобы безопасно, без ошибок пройти по массиву до нужного элемента, и получить значение этого элемента с нужным типом, без бойлерплейта.

Что значит безопасно?

Вместо:

$value = $response["GeocoderMetaData"]["Address"]["formatted"] ?? 'Адрес не найден';

Пишем:

$data = (new AdvancedArrayFactory())->makeAdvancedArray($response);
$value = $data->pull("GeocoderMetaData")->pull("Address")->get("formatted");
$value->default("Адрес не найден");

Чем это лучше ? Это лучше тем, что стена текста из перечисления индексов разбиваеться на отдельные индексы, которые визуально чётко разделены, такой код легче читать.

Что значит с нужным типом, без бойлерплейта?

Это значит, что нам не надо писать код для преобразования типа на случай если тип элемента отличается от ожидаемого.

Вместо:

$formatted = (string)$value;

Пишем:

$formatted = $value->str();

В целом, разница будет такой: было две строчки кода:

$formatted = (string)$response["GeocoderMetaData"]["Address"]["formatted"] 
  ?? 'Адрес не найден';

стало семь строчек:

$data = (new AdvancedArrayFactory())->makeAdvancedArray($response);
$formatted = $data
    ->pull("GeocoderMetaData")
    ->pull("Address")
    ->get("formatted")
    ->default("Адрес не найден")
    ->str();

Код получился более человекочитаемый, менее машиночитаемый (теперь IDE не понимает, что мы идём по индексам массива, и теперь IDE не может предсказать структуру массива).

Строчек кода стало больше, понятность выше. Вместо компактности получили понятность. По-моему стало лучше.

Многословный, но понятный код, намного лучше компактного кода, который работает не пойми как, пока его не запустишь, да можно ограничиться запуском у себя в голове, но зачем лишний раз напрягать мозг ? Это отвлекает внимание от основной задачи, не надо так делать, берегите своё "мыслетопливо"!

Предыстория

Я несколько лет назад опубликовал на Packagist composer-пакет sbwerewolf/language-specific, и написал на Хабре два статьи об этом пакете (статья1, статья2).

С августа 2023 года пакет начали активно скачивать, в среднем за месяц 500 установок, на январь 2025 года уже 8 000 установок.

Альтернатива

В декабре 2024 на Хабре была опубликована статья "PHP Typed" про библиотеку которая помогает получить значения глубоко вложенных элементов PHP-массивов или глубоко вложенных свойств PHP-классов (stdClass). Это ровно то что делает мой пакет sbwerewolf/language-specific.

Конечно мне было интересно сравнить эти два решения. Я написал бенчмарк, вывод: Typed на простых случаях в два раза быстрей, в сложных, наверное будет больше, не проверял.

Я заглянул под капот в Typed, посмотрел что у меня написано в ArrayHandler.

Разница между Typed и ArrayHandler в том, что Typed только выдаёт значение элемента в занном типе. ArrayHandler не только выдаёт значение в заданном типе, но и позволяет принять решение в ситуации когда значения нет, или поверить фактический тип элемента, и в зависимости от этого провести дальнейшую обработку. Например если элемент это bool со значением false, то ни чего не делаем, а если элемент это array, то проводим дальнейшую обработку всех элементов.

Typed это расширение базового набора PHP для работы с массивами.

ArrayHandler это работа с массивом в ООП стиле, ArrayHandler объединяет в себе данные и методы для работы с ними.

Если вы уверены в структуре массива с которым работает - используйте Typed.

Если структура массива имеет большую вариантивность и в зависмости от варианта должна быть обработана по разному - используйте ArrayHandler.

Доработки

Когда я сравнивал Typed и ArrayHandler, я увидел что у ArrayHandler покрытие тестами хромает: покрытие кода упало до 76%, потому что после добавления поддержки интерфейсов ArrayAccess, Iterator, JsonSerializable, код методов добавился, а код тестов - нет.

Я решил это дело исправить, хотел добавить пару простых тестов, понял что ArrayHandler перегружен функционалом, поделил его на несколько классов, переименовал методы и пошло поехало. Добавил интерфейсы, добавил фабрики. Добавил пакеты для статического анализа, короче в Новый Год развлекался на полную.

Как установить

composer require sbwerewolf/language-specific

Как пользоваться

Собственно основной use-case был показан выше. Допустим нам из API пришёл большой JSON, и мы хотим из него часть информации записать себе в БД, для этого надо эту часть информации извлечь из JSON, конечно с обработкой вариантов когда JSON не содержит нужной нам информации.

Запросим в Яндекс Геокодер адрес для "Mohammed Bin Rashid Boulevard 1":

$response = file_get_contents('https://geocode-maps.yandex.ru/1.x/?apikey=d3893dc1-c136-4084-ab9c-4db26b00463e&geocode=Mohammed+Bin+Rashid+Boulevard+1&lang=en_US&format=json');
var_dump( $response);
/* string(3197) "{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"Mohammed Bin Rashid Boulevard 1","results":"10","found":"2"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"precision":"exact","text":"1, Mohammed Bin Rashid Boulevard, Downtown Dubai, Dubai, United Arab Emirates","kind":"house","Address":{"country_code":"AE","formatted":"1, Mohammed Bin Rashid Boulevard, Downtown Dubai, Dubai, United Arab Emirates","Components":[{"kind":"country","name":"United Arab Emirates"},{"kind":"province","name":"Dubai"},{"kind":"area","name":"Sector 3"},{"kind":"district","name":"Downtown Dubai"},{"kind":"district","name":"Downtown Dubai"},{"kind":"street","name":"Mohammed Bin Rashid Boulevard"},{"kind":"house","name":"1"}]},"AddressDetails":{"Country":{"AddressLine":"1, Mohammed Bin Rashid Boulevard, Downtown Dubai, Dubai, United Arab Emirates","CountryNameCode":"AE","CountryName":"United Arab Emirates","AdministrativeArea":{"AdministrativeAreaName":"Dubai","SubAdministrativeArea":{"SubAdministrativeAreaName":"Sector 3","Locality":{"DependentLocality":{"DependentLocalityName":"Downtown Dubai","DependentLocality":{"DependentLocalityName":"Downtown Dubai","Thoroughfare":{"ThoroughfareName":"Mohammed Bin Rashid Boulevard","Premise":{"PremiseNumber":"1"}}}}}}}}}}},"name":"1, Mohammed Bin Rashid Boulevard","description":"Downtown Dubai, Dubai, United Arab Emirates","boundedBy":{"Envelope":{"lowerCorner":"55.270141 25.193445","upperCorner":"55.278352 25.200915"}},"uri":"ymapsbm1://geo?data=CgoyMTE3NTQxODgxEocB2KfZhNil2YXYp9ix2KfYqiDYp9mE2LnYsdio2YrYqSDYp9mE2YXYqtit2K_YqSwg2KXZhdin2LHYqSDYr9io2YosINmI2LPYtyDZhdiv2YrZhtipINiv2KjZiiwg2KjZiNmE2YrZgdin2LHYryDZhdit2YXYryDYqNmGINix2KfYtNivLCAxIgoN1BhdQhXVk8lB","Point":{"pos":"55.274247 25.19718"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"precision":"street","text":"Mohammed Bin Rashid Boulevard, Downtown Dubai, Dubai, United Arab Emirates","kind":"street","Address":{"country_code":"AE","formatted":"Mohammed Bin Rashid Boulevard, Downtown Dubai, Dubai, United Arab Emirates","Components":[{"kind":"country","name":"United Arab Emirates"},{"kind":"province","name":"Dubai"},{"kind":"area","name":"Sector 3"},{"kind":"district","name":"Downtown Dubai"},{"kind":"street","name":"Mohammed Bin Rashid Boulevard"}]},"AddressDetails":{"Country":{"AddressLine":"Mohammed Bin Rashid Boulevard, Downtown Dubai, Dubai, United Arab Emirates","CountryNameCode":"AE","CountryName":"United Arab Emirates","AdministrativeArea":{"AdministrativeAreaName":"Dubai","SubAdministrativeArea":{"SubAdministrativeAreaName":"Sector 3","Locality":{"DependentLocality":{"DependentLocalityName":"Downtown Dubai","Thoroughfare":{"ThoroughfareName":"Mohammed Bin Rashid Boulevard"}}}}}}}}},"name":"Mohammed Bin Rashid Boulevard","description":"Downtown Dubai, Dubai, United Arab Emirates","boundedBy":{"Envelope":{"lowerCorner":"55.277606 25.195668","upperCorner":"55.279807 25.199027"}},"uri":"ymapsbm1://geo?data=Cgo1ODA2NzkyODQ1EkpVbml0ZWQgQXJhYiBFbWlyYXRlcywgRHViYWksIERvd250b3duIER1YmFpLCBNb2hhbW1lZCBCaW4gUmFzaGlkIEJvdWxldmFyZCIKDXQdXUIVGZTJQQ,,","Point":{"pos":"55.278765 25.197311"}}}]}}}" */

$object = json_decode(json: $response, associative: true, flags: JSON_THROW_ON_ERROR);
$data = new AdvancedArrayFactory()->makeAdvancedArray($object);
$formatted = $data
    ->pull("response")
    ->pull("GeoObjectCollection")
    ->pull("featureMember")
    ->pull()
    ->pull("GeoObject")
    ->pull("metaDataProperty")
    ->pull("GeocoderMetaData")
    ->pull("Address")
    ->get("formatted")
    ->default("Адрес не найден");
$val = $formatted->str();
var_dump( $val);
/* 
string(77)
"1, Mohammed Bin Rashid Boulevard, Downtown Dubai, Dubai, United Arab Emirates"
*/
$isReal = $formatted->isReal();
var_dump( $isReal);
/* bool(true) */

Распарсим ответ:

  1. преобразуем строковое представление JSON в массив

  2. из массива с помощью фабрики делаем экземпляр AdvancedArray

  3. AdvancedArray::pull() - получаем экземпляр AdvancedArray для вложенного массива, делаем так пока не дойдём до нужного элемента

  4. AdvancedArray::get() - получаем экземпляр CommonValue для элемента formatted (не массив)

  5. CommonValue::str() - получаем значение элемента:"1, Mohammed Bin Rashid Boulevard, Downtown Dubai, Dubai, United Arab Emirates"

  6. CommonValue::isReal() - поверяем что значение действительное, а не подставлено по умолчанию: true - значение действительное

Запросим в Яндекс Геокодер адрес для "Адский остров" (данные отсутствуют):

$response = file_get_contents('https://geocode-maps.yandex.ru/1.x/?apikey=d3893dc1-c136-4084-ab9c-4db26b00463e&geocode=%D0%B0%D0%B4%D1%81%D0%BA%D0%B8%D0%B9+%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B2&lang=en_US&format=json');
var_dump( $response);
/*
string(172)
"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":
{"request":"адский остров","results":"10","found":"0"}},"featureMember":[]}}}"
*/

$object = json_decode(json: $response, associative: true, flags: JSON_THROW_ON_ERROR);
$data = (new AdvancedArrayFactory())->makeAdvancedArray($object);
$formatted = $data
    ->pull("response")
    ->pull("GeoObjectCollection")
    ->pull("featureMember")
    ->pull()
    ->pull("GeoObject")
    ->pull("metaDataProperty")
    ->pull("GeocoderMetaData")
    ->pull("Address")
    ->get("formatted")
    ->default("Адрес не найден");
$value = $formatted->str();
var_dump( $value);
/* string(28) "Адрес не найден" */
$isReal = $formatted->isReal();
var_dump( $isReal);
/* bool(false) */

Распарсим ответ:

  1. преобразуем строковое представление JSON в массив

  2. из массива с помощью фабрики делаем экземпляр AdvancedArray

  3. AdvancedArray::pull() - получаем экземпляр AdvancedArray для вложенного массива, делаем так пока не дойдём до нужного элемента

  4. AdvancedArray::get() - получаем экземпляр CommonValue для элемента formatted (не массив)

  5. CommonValue::str() - получаем значение элемента:"Адрес не найден"

  6. CommonValue::isReal() - поверяем что значение действительное, а не подставное: false - значение не действительное, подставлено значение по умолчанию.

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

Разобраться в том как происходит парсинг легко - это важно для понимания и поддержки кода.

Алгоритм очень простой:

  1. Последовательным вызовом метода AdvancedArray::pull($index) получить нужный массив

  2. Методом AdvancedArray::get($index) получить значение элемента

  3. С помощью соответствующего метода приведения типа, получить значение с нужным типом

Дополнительные возможности

Дополнительные возможности пакета sbwerewolf/language-specific это возможность проверки наличия элемента по индексу и встроенная возможность проверить тип элемента, так же можно получить значение элемента без преобразования типа, то есть получить значение как есть.

Кроме того можно получить все "элементы-значения" (не массивы), и соответствено, можно получить все элементы-массивы.

Проверить наличие элемента (существование)

$data = (new AdvancedArrayFactory())->makeAdvancedArray($array);
$element = $data
    ->pull("response")
    ->pull("GeoObjectCollection")
    ->pull("featureMember")
    ->pull()
    ->pull("GeoObject")
    ->pull("metaDataProperty")
    ->pull("GeocoderMetaData")
    ->pull("Address")
$hasElement = $element->has("formatted"); // bool

Можно проще:

$data = (new AdvancedArrayFactory())->makeAdvancedArray($array);
$formatted = $data
    ->pull("response")
    ->pull("GeoObjectCollection")
    ->pull("featureMember")
    ->pull()
    ->pull("GeoObject")
    ->pull("metaDataProperty")
    ->pull("GeocoderMetaData")
    ->pull("Address")
    ->get("formatted");
$isReal = $formatted->isReal(); // bool

Получить тип

$data = (new AdvancedArrayFactory())->makeAdvancedArray($array);
$formatted = $data
    ->pull("response")
    ->pull("GeoObjectCollection")
    ->pull("featureMember")
    ->pull()
    ->pull("GeoObject")
    ->pull("metaDataProperty")
    ->pull("GeocoderMetaData")
    ->pull("Address")
    ->get("formatted");
$type = $formatted->type(); 
// NULL если элемента нет и если элемент есть, то "1, Mohammed Bin Rashid ..."

В общем случае будет выдан тип элемента, в случае с ГеоКодером это string.

Если элемент отсутствует, то тип будет NULL.

Если задано значение по умолчанию и элемент отсутствует, то будет выдан тип значения по умолчанию.

Если задано значение по умолчанию и элемент присутствует, то будет выдан тип значения элемента и не факт что он будет совпадать с типом значения по умолчанию.

Получить значение как есть, без приведения типа

$data = (new AdvancedArrayFactory())->makeAdvancedArray($array);
$formatted = $data
    ->pull("response")
    ->pull("GeoObjectCollection")
    ->pull("featureMember")
    ->pull()
    ->pull("GeoObject")
    ->pull("metaDataProperty")
    ->pull("GeocoderMetaData")
    ->pull("Address")
    ->get("formatted")
  ;
$value = $formatted->asIs();
// NULL если элемента нет, если элемент есть, то "1, Mohammed Bin Rashid ..."

Значение элемента будте выдано как есть, если элемента нет, то будет выдано значение null, если задано значение по умолчанию, то будет выдано значение по умолчанию.

Получить все элементы-значения

$data = [1, ['first', 'next',],
    2, ['next','last']];

$fabric = new AdvancedArrayFactory();
$handler = (new AdvancedArrayFactory())->makeAdvancedArray($data);

$index = 0;
$item = $fabric::makeDummyAdvancedArray();
foreach ($handler->values() as $item) {
    var_dump( $item->asIs());
}
/*
int(1)
int(2)
*/

Получить все элементы-массивы

$data = [1, ['first', 'next',],
    2, ['next','last']];

$fabric = new AdvancedArrayFactory();
$handler = (new AdvancedArrayFactory())->makeAdvancedArray($data);

$index = 0;
$item = $fabric::makeDummyAdvancedArray();
foreach ($handler->arrays() as $item) {
    var_dump( $item->raw());
}
/*
array(2) {
  [0]=>
  string(5) "first"
  [1]=>
  string(4) "next"
}
array(2) {
  [0]=>
  string(4) "next"
  [1]=>
  string(4) "last"
}
*/

Состав пакета sbwerewolf/language-specific

Пакет состоит из 4 классов:

  • CommonValue предоставляет значение элемента, тип значения будет приведён к заданному

  • BaseArray обёртка над массивом, реализация интерфейсов Iterator и JsonSerializable

  • CommonArray класс наследник от BaseArray , реализация интерфейса ArrayAccess

  • AdvancedArray класс наследник от CommonArray, работа с вложенными массивами

  • Три фабрики для создания экземпляров классов надлежащим образом, это: CommonValueFactory , ArrayFactory, AdvancedArrayFactory

  • один вспомогательный класс и один класс DTO, интерфейсы на всё (больше ради "православного" тестирования)

CommonValue

Этот класс является сутью пакета, именно этот класс обеспечивает предоставление значения с заданным типом, класс гарантирует, что:

  • значение будет приведено к заданному типу,

  • при необходимости будет подставлено значение по умолчанию,

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

Методы класса CommonValue

  • CommonValue::default() задать значение по умолчанию

  • CommonValue::isReal() проверяет, что значение является действительным (исследовательский метод)

  • CommonValue::type() получить действительный тип значения (исследовательский метод)

  • CommonValue::asIs() предоставить значение как есть, без приведения типа

  • CommonValue::double() предоставить значение приведённое к типу float, методы называется double, потому что вызов gettype((float)null) вернёт double

  • CommonValue::str() вернёт значение приведённое к типу string

  • CommonValue::int() вернёт значение приведённое к типу int

  • CommonValue::bool() вернёт значение приведённое к типу bool

  • CommonValue::array() вернёт значение приведённое к array

  • CommonValue::object() вернёт значение приведённое к object

BaseArray

BaseArray возвращает элементы массива только как экземпляры CommonValueInterface, то есть перебрать вложенные массивы не получиться.

Класс является реализаций интерфейсов Iterator и JsonSerializable.

Можно применить json_encode() , массив будет конвертирован в JSON-строку.

Можно прогонять экземпляр через forech, можно вручную перебрать элементы массива (с помощью методов next() key() current() rewind() ).

Пример с foreach:

$data = (new AdvancedArrayFactory())->makeAdvancedArray([2,3,4,[5,'q'],'w',['e','r']]);
foreach ($data as $element){ var_dump($element->asIs()); }
/*
int(2)
int(3)
int(4)
array(2) {
  [0]=>
  int(5)
  [1]=>
  string(1) "q"
}
string(1) "w"
array(2) {
  [0]=>
  string(1) "e"
  [1]=>
  string(1) "r"
}
*/

До версии 8.4 при переборе элементов массива с помощью foreach возвращалось собственно значение, в версии 8.4 поведение было изменено, теперь значение возвращаеться как экземпляр класса с интерфейсом CommonValueInterface, для получения значения без приведения типа служит метод CommonValueInterface::asIs()

С помощью метода BaseArray::raw():array можно вернуть исходный массив целиком.

В целом ни чего интересного, класс сделан что бы в основном классе AdvancedArray не было "неинтересного кода".

Для создания экземпляров класса BaseArray можно использовать фабрику ArrayFactory.

CommonArray

Ещё один неинтересный класс, расширяет функционал класса BaseArray методами интерфейса ArrayAccess (работа с элементами массив через квадрантные скобочки) при этом вызов методов для изменения или удаления элементов массива приведёт к вызову исключений ListIsImmutableException и ValueIsImmutableException, значения элементов менять запрещено, удалять элементы запрещено.

Класс добавляет два метода аналогичных методам интерфейса ArrayAccess :

  • CommonArray::has(string|int|float|bool|null $key = null): bool проверить существование элемента по индексу, если вызвать без аргументов, то вернёт true если в массиве есть хотя бы один элемент, метод являтьеся аналогом ArrayAccess::offsetExists()

  • CommonArray::get(string|int|float|bool|null $key = null) получить элемент по индексу (элемент будет предоставлен как экземпляр CommonValue), если индекс не задавать, то будет возвращён случайный элемент (если в массиве есть хотя бы один элемент), если в массиве нет элементов, то метод вернёт CommonValue с недействительным значением (CommonValue::isReal() => false), метод являтьеся аналогом ArrayAccess::offsetGet()

Для создания экземпляров класса CommonArray можно использовать фабрику ArrayFactory.

AdvancedArray

Класс реализует доступ к элементам вложенных массивов, является наследником от CommonArray.

Методы класса AdvancedArray

  • AdvancedArray::isDummy() проверяет, что массив является подставным (заглушка для не существующих массивов) (исследовательский метод)

  • AdvancedArray::pull(int|bool|string|null|float $key = null) вытягивает вложенный массив, то есть возвращает новый экземпляр AdvancedArrayInterface для вложенного массива, если элемент с заданным индексом не являеться массивом, то будет возвращён AdvancedArrayInterface::isDummy() => True, если индекс не задавать, то будет возвращён новый экземпляр AdvancedArrayInterface от первого попавшегося вложенного массива, если вложенных массивов нет, то будет возвращён AdvancedArrayInterface::isDummy() => True

  • AdvancedArray::arrays() метод последовательно выдаёт все элементы-массивы, будет возвращён Generator, который выдаёт новый экземпляр AdvancedArrayInterface для всех вложенных массивов (следует использовать как foreach ( AdvancedArray::arrays() as $k => $v) )

  • AdvancedArray::values() метод последовательно выдаёт все элементы-значения (не массивы), будет возвращён Generator, который выдаёт новый экземпляр CommonValueInterface для всех элементов не являющимися массивами (следует использовать как foreach ( AdvancedArray::values() as $k => $v) )

Для создания экземпляров класса AdvancedArray можно использовать фабрику AdvancedArrayFactory.

Ведение версий пакета

Версии пакета привязаны к минимальной версии PHP. Semantic Versioning не соблюдается.

Версии пакета это минимальная версия PHP, нет разделения на major и minor. Изменение в minor могут нарушать обратную совместимость. Это важное обстоятельство, если будите использовать пакет sbwerewolf/language-specific , то пожалуйста указывайте точную версию, что бы у вашего кода не было проблем при обновлении пакетов (composer update).

Заключение

Прошу делиться в коментах вашими способами "типобезопасной работы" с массивами в PHP. Своим способом я поделился в этой статье. Может быть есть аккие то более зрелые способы ? или все моим опасения устарели и я отстал от жизни ? Пишите комментарии. Но без оффтопика.

Написанного выше более чем достаточно, что бы понять возможности sbwerewolf/language-specific, если у вас остались вопросы о том как этот пакет можно применить в конкретных use-case, то пишите мне в личку в ВК, потому что в коментах я могу отвечать один раз в сутки (спасибо всем кто ставит минусы, спасибо Хабру за лимиты на общение), и вы просто не дождётесь моего ответа.

спасибо за чтение.

Комментарии (7)


  1. fo_otman
    19.01.2025 07:34

    Отличная работа!


  1. TsarS
    19.01.2025 07:34

    Вот если бы каждый описывал свою библиотеку так же в Readme — мир стал бы лучше


  1. karrakoliko
    19.01.2025 07:34

    яй бы смотрел в сторону jsonpath-подобного синтаксиса, чтобы вместо цепоски pull'ов передавать строку типа "key?.nestedkey?.l2_nestedkey?"

    разворачивать обратно в голове цепочку pull'ов весьма не удобно


    1. SbWereWolf Автор
      19.01.2025 07:34

      Такой подход реализован в Typed, и в принципе это стандартный способ записи, только вопросики ни кто не пишет, и так понятно, что элемента может не быть.

      Но у моей библиотеки другое назначение, я не просто получаю значение, я его ещё и анализирую, было не было, и если было, то какого типа. Редкий сценарий использования, но как то с моим универсальным подходом получилось так. У Typed более практичный подход.


      1. karrakoliko
        19.01.2025 07:34

        Такой подход реализован в Typed

        киньте в меня ссылкой, пожалуйста.
        решительно не получается найти https://github.com/search?q=php typed&type=Everything&repo=&langOverride=&start_value=1


  1. Dhwtj
    19.01.2025 07:34

    Возьмут динамически типизированные языки, а потом мучаются


  1. koleso_O
    19.01.2025 07:34

    Всего 2 вопроса:

    1) Чем это решение лучше/хуже symfony serialiser?

    2) Есть ли возможность частичной десериализации (например, преобразовать часть json-строки в ассоциативный массив только обнаружения нудного ключа в исходной json-строке?