Пакет-географ – перша робоча версія

Перш за все хотів би подякувати за більш ніж 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% покриття, для областей і міст – менше, але постійно доповнюється. Для неперекладних міст пропонується додати можливість простої транслітерації.
Всі країни правильно схиляються – перевірено через онлайн-словники орфографії.
Плани на майбутнє
  1. Планується додати примітивний гео-індекс, щоб за координатами можна було отримати найближчий населений пункт.
  2. Різні мови, швидше за все, будуть рознесені в окремі репозиторії, щоб розробнику не було необхідності завантажувати непотрібні JSON-довідники. Більш того, JSON-довідники стануть незалежні від бібліотек-клієнтів – на них можна буде зав'язати майбутні клієнти Python і Ruby.
Місія проста – стати стандартною гео-бібліотекою веб-розробників. При досягненні достатньої популярності, можна очікувати від користувачів різних країн внесення поправок до перекази через pull-запити – довідники будуть самі постійно поліпшуватися, подібно wiki.
Буду дуже радий почути зауваження та побажання до API!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.