Типоспецифичное ядро фреймворка для SalesRender-плагинов типа GEOCODER
salesrender/plugin-core-geocoder -- специализированная библиотека-ядро, расширяющая базовый salesrender/plugin-core для создания плагинов типа Geocoder. Geocoder-плагины разрешают адреса в географические координаты, часовые пояса и структурированные адресные данные.
Данное ядро предоставляет:
- Интерфейс
GeocoderInterface, который разработчик должен реализовать с логикой конкретного сервиса геокодирования GeocoderContainerдля регистрации реализации геокодера- HTTP-эндпоинт (
POST /protected/geocoder/handle) для обработки запросов геокодирования - Объект-значение
GeocoderResultдля возврата структурированных результатов (адрес, часовой пояс, информация) - Класс
Timezone, поддерживающий как именованные часовые пояса, так и смещения UTC GeocoderAction, который парсит запросы и вызывает настроенный геокодер
composer require salesrender/plugin-core-geocoder- PHP >= 7.4
- ext-json
salesrender/plugin-core^0.4.0 (устанавливается автоматически)salesrender/component-address^1.0.0 (устанавливается автоматически)adbario/php-dot-notation^2.2 (устанавливается автоматически)
plugin-core-geocoder переопределяет оба класса фабрик из базового plugin-core:
WebAppFactory (наследует \SalesRender\Plugin\Core\Factories\WebAppFactory):
- Добавляет поддержку CORS
- Регистрирует
GeocoderActionпо маршрутуPOST /protected/geocoder/handleс protected middleware
ConsoleAppFactory (наследует \SalesRender\Plugin\Core\Factories\ConsoleAppFactory):
- Наследует все базовые команды без добавления новых (геокодирование синхронное, очередь не требуется)
SalesRender CRM Geocoder-плагин Внешний API
| | |
|-- POST /protected/geocoder/handle --->| |
| |-- GeocoderInterface::handle() ---->|
| |<-- GeocoderResult[] --------------|
|<-- JSON-массив GeocoderResult --------| |Создайте новый проект и добавьте зависимость:
mkdir my-geocoder-plugin && cd my-geocoder-plugin
composer init --name="myvendor/plugin-geocoder-myservice" --type="project"
composer require salesrender/plugin-core-geocoderСоздайте структуру директорий:
my-geocoder-plugin/
bootstrap.php
console.php
composer.json
example.env
public/
.htaccess
index.php
icon.png
src/
Geocoder.php
SettingsForm.php
db/
runtime/Создайте bootstrap.php в корне проекта. Этот файл конфигурирует все компоненты плагина:
<?php
use SalesRender\Plugin\Components\Db\Components\Connector;
use SalesRender\Plugin\Components\Form\Autocomplete\AutocompleteRegistry;
use SalesRender\Plugin\Components\Info\Developer;
use SalesRender\Plugin\Components\Info\Info;
use SalesRender\Plugin\Components\Info\PluginType;
use SalesRender\Plugin\Components\Settings\Settings;
use SalesRender\Plugin\Components\Translations\Translator;
use SalesRender\Plugin\Core\Geocoder\Components\Geocoder\GeocoderContainer;
use SalesRender\Plugin\Instance\Geocoder\Geocoder;
use SalesRender\Plugin\Instance\Geocoder\SettingsForm;
use Medoo\Medoo;
use XAKEPEHOK\Path\Path;
# 0. Настройте переменные окружения в файле .env в корне приложения
# 1. Настройка БД (для SQLite файл *.db и родительская директория должны быть доступны для записи)
Connector::config(new Medoo([
'database_type' => 'sqlite',
'database_file' => Path::root()->down('db/database.db')
]));
# 2. Установите язык плагина по умолчанию
Translator::config('ru_RU');
# 3. Настройте информацию о плагине
Info::config(
new PluginType(PluginType::GEOCODER),
fn() => Translator::get('info', 'Название плагина'),
fn() => Translator::get('info', 'Описание плагина в формате markdown'),
[
'countries' => ['RU'],
],
new Developer(
'Название вашей компании',
'support.for.plugin@example.com',
'example.com',
)
);
# 4. Настройте форму настроек
Settings::setForm(fn() => new SettingsForm());
# 5. Настройте автодополнения форм (или удалите этот блок, если не используется)
AutocompleteRegistry::config(function (string $name) {
return null;
});
# 6. Настройте GeocoderContainer, указав вашу реализацию геокодера
GeocoderContainer::config(new Geocoder());Ключевые параметры конфигурации для geocoder-плагинов:
PluginType::GEOCODER-- определяет тип плагина как Geocodercountries-- массив кодов стран ISO 3166-1 alpha-2, которые поддерживает данный геокодер (например,['RU'],['RU', 'KZ'])GeocoderContainer::config()-- регистрирует вашу реализациюGeocoderInterface
Это ключевая часть вашего geocoder-плагина. Создайте класс, реализующий GeocoderInterface:
<?php
namespace SalesRender\Plugin\Instance\Geocoder;
use SalesRender\Components\Address\Address;
use SalesRender\Components\Address\Location;
use SalesRender\Plugin\Core\Geocoder\Components\Geocoder\GeocoderInterface;
use SalesRender\Plugin\Core\Geocoder\Components\Geocoder\GeocoderResult;
use SalesRender\Plugin\Core\Geocoder\Components\Geocoder\Timezone;
class Geocoder implements GeocoderInterface
{
/**
* @param string $typing - свободный текстовый ввод пользователя
* @param Address $address - структурированные адресные данные
* @return GeocoderResult[]
*/
public function handle(string $typing, Address $address): array
{
// Вариант 1: если $typing не пуст, используйте его как свободный поиск
if (!empty(trim($typing))) {
// Вызовите API вашего сервиса геокодирования
// Преобразуйте ответ в объекты GeocoderResult
$resolvedAddress = new Address(
'Регион', // region
'Город', // city
'Улица, 1', // address_1
'', // address_2
'123456', // postcode
'RU', // countryCode
new Location(55.7558, 37.6173) // latitude, longitude
);
return [
new GeocoderResult(
$resolvedAddress,
new Timezone('Europe/Moscow'),
'Дополнительная информация о результате'
),
];
}
// Вариант 2: если $typing пуст, разрешите/улучшите структурированный $address
$handledAddress = new Address(
strtoupper($address->getRegion()),
strtoupper($address->getCity()),
strtoupper($address->getAddress_1()),
strtoupper($address->getAddress_2()),
strtoupper($address->getPostcode()),
$address->getCountryCode(),
$address->getLocation()
);
$timezone = null;
if ($address->getCountryCode() && !empty($address->getRegion())) {
$timezone = new Timezone('UTC+03:00');
}
return [new GeocoderResult($handledAddress, $timezone)];
}
}Метод handle() принимает два параметра:
$typing-- свободный текст, введённый пользователем (для поиска адреса в стиле автодополнения)$address-- структурированный объектAddressс полями region, city, address_1, address_2, postcode, countryCode и location
Метод должен вернуть массив объектов GeocoderResult. Каждый результат содержит разрешённый Address, необязательный Timezone и необязательную информационную строку.
Создайте public/index.php:
<?php
use SalesRender\Plugin\Core\Geocoder\Factories\WebAppFactory;
require_once __DIR__ . '/../vendor/autoload.php';
$factory = new WebAppFactory();
$application = $factory->build();
$application->run();Создайте public/.htaccess:
RewriteEngine On
RewriteRule ^output - [L]
RewriteRule ^uploaded - [L]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [L,QSA]Создайте console.php:
#!/usr/bin/env php
<?php
use SalesRender\Plugin\Core\Geocoder\Factories\ConsoleAppFactory;
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/bootstrap.php';
$factory = new ConsoleAppFactory();
$application = $factory->build();
$application->run();Создайте src/SettingsForm.php:
<?php
namespace SalesRender\Plugin\Instance\Geocoder;
use SalesRender\Plugin\Components\Form\FieldDefinitions\FieldDefinition;
use SalesRender\Plugin\Components\Form\FieldDefinitions\PasswordDefinition;
use SalesRender\Plugin\Components\Form\FieldDefinitions\StringDefinition;
use SalesRender\Plugin\Components\Form\FieldGroup;
use SalesRender\Plugin\Components\Form\Form;
use SalesRender\Plugin\Components\Form\FormData;
use SalesRender\Plugin\Components\Translations\Translator;
class SettingsForm extends Form
{
public function __construct()
{
$nonNull = function ($value, FieldDefinition $definition, FormData $data) {
$errors = [];
if (is_null($value)) {
$errors[] = Translator::get('settings', 'Поле не может быть пустым');
}
return $errors;
};
parent::__construct(
Translator::get('settings', 'Настройки'),
null,
[
'main' => new FieldGroup(
Translator::get('settings', 'Основные настройки'),
null,
[
'email' => new StringDefinition(
Translator::get('settings', 'Email'),
null,
$nonNull
),
'password' => new PasswordDefinition(
Translator::get('settings', 'Пароль'),
null,
$nonNull
),
]
),
],
Translator::get('settings', 'Сохранить'),
);
}
}Создайте example.env (скопируйте в .env для локальной разработки):
LV_PLUGIN_DEBUG=1
LV_PLUGIN_PHP_BINARY=php
LV_PLUGIN_QUEUE_LIMIT=1
LV_PLUGIN_SELF_URI=http://plugin-example/
LV_PLUGIN_COMPONENT_REGISTRATION_SCHEME=https
LV_PLUGIN_COMPONENT_REGISTRATION_HOSTNAME=lv-app# Установка зависимостей
composer install
# Создание таблиц базы данных
php console.php db:create
# Запуск cron (для базовых задач, таких как special requests)
php console.php cronМаршруты, добавляемые \SalesRender\Plugin\Core\Geocoder\Factories\WebAppFactory:
| Метод | Путь | Описание | Источник |
|---|---|---|---|
POST |
/protected/geocoder/handle |
Принимает запросы геокодирования. Парсит тело запроса в typing (string) и address (Address), вызывает GeocoderInterface::handle() и возвращает JSON-массив объектов GeocoderResult. Защищён middleware. |
GeocoderAction |
Также наследуются все базовые маршруты plugin-core:
| Метод | Путь | Описание |
|---|---|---|
GET |
/info |
Информация о плагине |
PUT |
/registration |
Регистрация плагина |
GET |
/protected/forms/settings |
Определение формы настроек |
PUT |
/protected/data/settings |
Сохранение настроек |
GET |
/protected/data/settings |
Получение данных настроек |
GET |
/protected/autocomplete/{name} |
Обработчик автодополнения |
GET |
/robots.txt |
Robots.txt |
POST /protected/geocoder/handle ожидает следующее JSON-тело:
{
"typing": "Москва Красная площадь",
"address": {
"region": "",
"city": "",
"address_1": "",
"address_2": "",
"building": "",
"apartment": "",
"postcode": "",
"countryCode": "RU",
"location": {
"latitude": null,
"longitude": null
}
}
}Возвращает JSON-массив результатов геокодирования:
[
{
"address": {
"region": "Московская область",
"city": "Москва",
"address_1": "Красная площадь, 1",
"address_2": "",
"postcode": "109012",
"countryCode": "RU",
"location": {
"latitude": 55.7539,
"longitude": 37.6208
}
},
"timezone": {
"name": "Europe/Moscow",
"offset": null
},
"info": "Дополнительная информация"
}
]| Код | Описание |
|---|---|
400 |
Некорректные адресные данные в запросе |
417 |
GeocoderHandleException -- специфичная для геокодера ошибка при обработке |
501 |
Геокодер не настроен (в GeocoderContainer нет обработчика) |
Ядро для геокодера не добавляет новых CLI-команд помимо унаследованных от базового plugin-core:
| Команда | Описание |
|---|---|
db:create |
Создание таблиц базы данных |
db:clean |
Очистка таблиц базы данных |
specialRequest:queue |
Обработка очереди специальных запросов |
specialRequest:handle |
Обработка специального запроса |
cron |
Запуск всех запланированных cron-задач |
lang:add |
Добавление языка перевода |
lang:update |
Обновление переводов |
directory:clean |
Очистка временных директорий |
Namespace: SalesRender\Plugin\Core\Geocoder\Components\Geocoder\GeocoderInterface
Основной интерфейс, который должен реализовать каждый geocoder-плагин:
use SalesRender\Components\Address\Address;
interface GeocoderInterface
{
/**
* @param string $typing - свободный текстовый ввод
* @param Address $address - структурированные адресные данные
* @return GeocoderResult[]
*/
public function handle(string $typing, Address $address): array;
}Namespace: SalesRender\Plugin\Core\Geocoder\Components\Geocoder\GeocoderResult
Объект-значение, представляющий один результат геокодирования. Реализует JsonSerializable.
| Метод | Тип возврата | Описание |
|---|---|---|
__construct(Address $address, ?Timezone $timezone, ?string $info = null) |
Создание результата с адресом, необязательным часовым поясом и необязательной информацией | |
getAddress() |
Address |
Разрешённый/улучшенный адрес |
getTimezone() |
?Timezone |
Разрешённый часовой пояс (если доступен) |
getInfo() |
?string |
Дополнительный информационный текст о результате |
Namespace: SalesRender\Plugin\Core\Geocoder\Components\Geocoder\GeocoderContainer
Статический контейнер для регистрации и получения реализации GeocoderInterface.
| Метод | Тип возврата | Описание |
|---|---|---|
config(GeocoderInterface $geocoder) |
void |
Регистрация реализации геокодера |
getHandler() |
GeocoderInterface |
Получение зарегистрированного геокодера. Выбрасывает GeocoderContainerException, если не настроен. |
Namespace: SalesRender\Plugin\Core\Geocoder\Components\Geocoder\Timezone
Представляет часовой пояс, принимая либо именованный часовой пояс, либо смещение UTC. Реализует JsonSerializable.
| Метод | Тип возврата | Описание |
|---|---|---|
__construct(string $timezoneOrOffset) |
Создание из названия часового пояса (например, "Europe/Moscow") или смещения UTC (например, "UTC+03:00"). Выбрасывает InvalidTimezoneException при невалидном значении. |
|
getName() |
?string |
Название часового пояса (например, "Europe/Moscow") или null, если создан из смещения |
getOffset() |
?string |
Смещение UTC (например, "UTC+03:00") или null, если создан из названия |
Примеры:
// Из названия часового пояса
$tz = new Timezone('Europe/Moscow');
$tz->getName(); // "Europe/Moscow"
$tz->getOffset(); // null
// Из смещения UTC
$tz = new Timezone('UTC+03:00');
$tz->getName(); // null
$tz->getOffset(); // "UTC+03:00"
// Невалидное значение -- выбрасывает InvalidTimezoneException
$tz = new Timezone('Invalid/Zone');Формат смещения должен соответствовать паттерну UTC[+-]\d{2}:\d{2} (например, UTC+03:00, UTC-05:00). Именованные часовые пояса должны быть валидными идентификаторами PHP DateTimeZone.
Namespace: SalesRender\Plugin\Core\Geocoder\GeocoderAction
HTTP-действие, обрабатывающее POST /protected/geocoder/handle. Реализует ActionInterface. Парсит тело запроса с использованием dot notation (через Adbar\Dot), конструирует объект Address с необязательным Location и вызывает геокодер.
Действие выполняет:
- Получение геокодера из
GeocoderContainer::getHandler() - Извлечение
typingиз тела запроса - Конструирование
Addressиз полейaddress.*, включая необязательныйLocation(latitude/longitude) - Вызов
GeocoderInterface::handle($typing, $address) - Возврат массива результатов в формате JSON
| Исключение | Namespace | Описание |
|---|---|---|
GeocoderContainerException |
SalesRender\Plugin\Core\Geocoder\Exceptions |
Выбрасывается при вызове GeocoderContainer::getHandler() до конфигурации |
GeocoderHandleException |
SalesRender\Plugin\Core\Geocoder\Exceptions |
Должно выбрасываться реализацией геокодера при возникновении ожидаемой ошибки в процессе геокодирования. Приводит к HTTP-ответу 417. |
InvalidTimezoneException |
SalesRender\Plugin\Core\Geocoder\Exceptions |
Выбрасывается при создании Timezone с невалидным названием или смещением |
Смотрите эталонную реализацию: plugin-example-geocoder
plugin-example-geocoder/
bootstrap.php -- Конфигурация плагина и регистрация Geocoder
console.php -- Консольная точка входа (ConsoleAppFactory)
public/
index.php -- Веб-точка входа (WebAppFactory)
.htaccess -- Правила перенаправления Apache
icon.png -- Иконка плагина
src/
Geocoder.php -- Реализация GeocoderInterface
SettingsForm.php -- Определение формы настроек
db/ -- Директория базы данных SQLite
example.env -- Шаблон переменных окружения| Пакет | Версия | Назначение |
|---|---|---|
salesrender/plugin-core |
^0.4.0 | Базовый фреймворк плагинов |
salesrender/component-address |
^1.0.0 | Объекты-значения Address и Location |
adbario/php-dot-notation |
^2.2 | Доступ к вложенным данным запроса через dot notation |
- salesrender/plugin-core -- Базовый фреймворк плагинов
- salesrender/component-address -- Компонент адреса
- plugin-example-geocoder -- Пример реализации geocoder-плагина