Прежде всего хотел бы поблагодарить за более, чем 80 звёзд на GitHub, которые мне дали читатели Хабра по результатам предыдущего поста. И это несмотря на то, что репозиторий был почти пустой, а ссылка была неочевидна. На лицо полезность этого пакета!
Для тех, кто пропустил первый пост, маленькое повторение. Если у Вас в приложении есть что-то вроде:
Или что-то такое (ВК вообще не смог перевести Южный Мельбурн):
То встречайте (стучат барабаны) – библиотека Географ доступна в PHP-версии. В данной статье я покажу на примере собственного сайта плюсы перехода на новый пакет. Собственно, так и пришла мысль создать библиотеку – я заметил, что начинаю частенько повторять один и тот же функционал в разных приложениях, а повторять сегодня в мире разработчиков – ну просто как-то немодно.
Установка
Установить пакет можно одной командой, так как он опубликован в Packagist:
composer require menarasolutions/geographer
Никаких зависимостей нет – это является одним из главных принципов разработки на текущий момент. Не хочется обязывать пользователей пакета устанавливать дополнительное ПО или другие пакеты. Тем не менее, планируется добавить опциональные интеграции – Memcached, MongoDB.
Пример 1: простой список стран
Самый банальный пример, встречается на огромном количестве сайтов. Разработчику необходимо показать выпадающий список стран мира, вероятно с поддержкой разных языков.
Как было в моём приложении:
public static function getCountryNameByCode($countryCode, $language)
{
return Config::get('texts.countries')[$language][$countryCode];
}
Тут достаточно всё банально – класс-фасад Config даёт доступ к массивам, указанным в текстовых файлах, а далее мы по ключам языка и кода страны получаем необходимый перевод. Проще некуда, точно также делали, наверняка, многие.
Минусы у такого подхода:
– Было необходимо держать эти переводы внутри своего приложения, а прямого отношения к бизнес-логике они не имеют; – В начале все переводы необходимо добавлять вручную. Я не могу просто взять и начать работать с новым языком;
– Читать код возможно, но он не слишком интуитивный.
При переходе на библиотеку-географ стало:
public static function getCountryNameByCode($countryCode, $language)
{
return Geographer::findOneByCode($countryCode)
->setLanguage($language)
->getName();
}
Обретённые плюсы:
– Теперь переводы находятся вне приложения и время от времени они сами обновляются и улучшаются;
– Доступны многие популярные языки сразу "из коробки";
– Код стал более интуитивным, простым к прочтению;
– Есть возможность бросать подходящий exception на конкретной стадии – не найдена страна, не найден язык.
Пример 2: название пункта в правильной форме
А вот это уже сложнее, и здесь плюсы перехода на отдельную библиотеку намного заметнее. У меня на сайте есть страницы с подобными ссылками:
Или такими SEO-оптимизированными замечаниями:
Самое простое, банальное решение – добавить ещё несколько массивов или справочников в наше приложение, на каждую форму слова. Таким образом, у нас уже сотни или тысячи переводов появятся, и многие из них придётся добавлять или править вручную – большинство каталогов вроде Geonames не предоставляют склонений.
Может получится что-то вроде:
public static function getCountryNameByCode($countryCode, $language, $form = 'default')
{
return Config::get('texts.countries')[$language][$countryCode][$form];
}
Но иногда нужной формы не будет и мы захотим добавить какие-то условия – скажем, если нет правильной формы "из", то выводим предлог "из" и стандартную форму, вероятно меняя её окончание. И метод потихоньку превратиться в монстра с кучей условий, либо нам надо будет добавить новые классы – а наше приложение должно фокусироваться на чём-то совсем другом.
Но и это ещё не всё – большинство из нас используют на сайтах шаблоны и текстовые файлы, и возникнет вопрос, где хранить предлог – в справочнике стран (или городов) или в строке-шаблоне. То есть, иметь шаблон вроде "События в: город" или "События: город". В первом случае возникнут нюансы с названиями, которые требуют отличных предлогов, вроде "во Франции". Во втором, будет огромное количество повторений в словарях, либо дополнительная логика в коде.
В случае использования моей библиотеки:
public static function getCountryNameByCode($countryCode, $language, $form = 'default')
{
return Geographer::findOneByCode($countryCode)
->inflict($form)
->setLanguage($language)
->getName();
}
Предлоги можно включать и отключать методами includePrepositions()
и excludePrepositions()
, что позволяет использовать библиотеку в любых шаблонах. Думать о том, какой предлог правильный не надо. Заботиться о том, как текущий язык склоняет имена стран и меняются ли от этого предлоги – не надо.
Краткий обзор API
Методы на коллекциях
Массивы подразделений (стран, областей или городов) реализованы через популярные сегодня коллекции – умные массивы, поддерживающие Fluent API:
$states->sortBy('name'); // Отсортировать области по имени
$states->setLanguage('ru')->sortBy('name'); // По русским именам
$states->find(['code' => 472039]); // Найти все совпадения по параметрам
$states->findOne(['code' => 472039]); // Вернуть только первое совпадение
$states->findOneByCode(472039); // Волшебный метод для удобства
Общие методы
Все классы подразделений являются потомками одного класса и имеют общие методы:
$object->toArray(); // Вернуть в виде обычного массива
$object->parent(); // Вернуть родителя (город вернёт область, штат вернёт страну)
$object->getCode(); // Уникальный ID
$object->getShortName(); // Стандартное для языка название
$object->getLongName(); // Официальное, государственное название
Все данные о подразделении можно получать разными способами:
$object->getName(); // Через метод (при необходимости будет склонено)
$object->name; // Тоже самое
$object['name']; // Можно и как массив
$object->toArray()['name']; // Можно вытащить из примитивного массива
Класс-планета
$earth->getAfrica(); // Страны Ффрики
$earth->getEurope(); // Европейские страны
$earth->getNorthAmerica(); // Северная Америка и так далее
$earth->getSouthAmerica();
$earth->getAsia();
$earth->getOceania();
$earth->getCountries(); // Все страны мира
$earth->withoutMicro(); // Только страны с населением от 100,000
Связь между библиотекой и приложением
Если мы вынесем все данные о географических единицах в отдельную библиотеку, то мы сможем смело почистить свои массивы (или базу данных, или что-то ещё), но нам всё-таки надо как-то фиксировать связь между конкретным городом (или страной или областью) записи в нашей БД с записью в библиотеке.
Долгосрочная политика библиотеки – предоставить разработчику как можно больше уникальных идентификаторов, чтобы разработчик мог сам выбрать за что зацепиться (причем, вероятно, добавлять новые поля в БД даже не придется).
На текущий момент страны имеют коды ISO 3611-2, ISO 3611-3 и Geonames. Области имеют коды ISO 3166, FIPS и Geonames. Города имеют только коды Geonames – это самое негибкое место.
Таким образом, чтобы вывести на сайте, скажем, город пользователя, мы можем хранить geonames_id
в таблице пользователей, а по нему восстанавливать объект:
$city = City::build($geonames_id);
Большинство современных фреймворков смогут делать такое преобразование даже автоматически. Я специально выбрал различные международные системы идентификации – разработчик и его приложения не должны быть привязаны к библиотеке Географ. От неё отказаться должно быть также просто, как и начать ей пользоваться.
Покрытие на сегодня
В базе имеются все города мира с населением выше 50 тысяч человек, все области и страны.
Каждая страна имеет данные:
- идентификаторы ISO 3611-2 и 3611-3, Geonames;
- размер территории;
- национальная валюта;
- телефонный код;
- население;
- континент;
- официальный язык;
- различные формы названия страны.
Города и области имеют названия и уникальные идентификаторы.
Названия переведены на языки: русский, английский, испанский, итальянский, французский, китайский (путунхуа).
Для стран это 100% покрытие, для областей и городов – меньше, но постоянно дополняется. Для непереводимых городов предлагается добавить возможность простой транслитерации.
Все страны правильно склоняются – проверено через онлайн-словари орфографии.
Планы на будущее
Планируется добавить примитивный гео-индекс, чтобы по координатам можно было получить ближайший населенный пункт.
- Разные языки, скорее всего, будут разнесены в отдельные репозитории, чтобы разработчику не было необходимости скачивать ненужные JSON-справочники. Более того, JSON-справочники станут независимы от библиотек-клиентов – на них можно будет завязать будущие клиенты Python и Ruby.
Миссия простая – стать стандартной гео-библиотекой веб-разработчиков. При достижении достаточной популярности, можно ожидать от пользователей разных стран внесения поправок в переводы через pull-запросы – справочники будут сами постоянно улучшаться, подобно wiki.
Буду очень рад услышать замечания и пожелания к API!