Пакет-географ – первая рабочая версия
Прежде всего хотел бы поблагодарить за более, чем 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!
Комментарии (5)
2 июля 2016 в 12:05
0↑
↓
В принципе, вполне годная для использования библиотека, код вполне качественный, да и использовать удобно. Но возникло, как говориться одно «но»:"php": ">=5.6.0"
да, php 5.5 сейчас уже вышел из «active support» и перешел в «security updates only», но некоторые твердолобые до сих пор его используют и объяснить им что срочно необходимо обновляться до 5.6/7.0 проблематично. Есть ли возможность ввести поддержку 5.5?
Еще 1 вопрос о поддержке laravel/lumen — может стоит оформить отдельным пакетом, а базовый выделить как «standalone» и его уже использовать в require для пакета laravel/lumen?2 июля 2016 в 12:18
0↑
↓
Ох, я сходу уже не помню — там что-то требовало 5.6. У меня изначально стояло 5.5, пока не наткнулся :) Я проверю в общем!Про Laravel/Lumen — да, вероятно вы правы. Кроме того, там настолько простая интеграция — её чуть ли не проще в документации описать :)
2 июля 2016 в 12:31
0↑
↓
Я смотрел, правда очень бегло, но не нашел массового использования основных «фишек» 5.6 — плавающего кол-ва параметров аргументов фу-ии или использования «default», отлова «несуществующих» каллбэков тоже вроде не заметил через try-catch над object, deprecated function вам вовсе рано использовать, так что вам думаю видней в чем там загвоздка была.2 июля 2016 в 12:33
0↑
↓
Вспомнил — у меня тесты все в Travis запускаются. И изначально там 5.5 стояло в том числе — оно фейлилось как раз. Сейчас верну в Travis 5.5 и увижу :)2 июля 2016 в 12:40
0↑
↓
Нашел — это PhpUnit 5-ый не поддерживает 5.5. Сейчас попробую в 'require' сделать 5.5, а в 'require-dev' 5.6.В общем, я уже закомиттил и пометил