По умолчанию вместе с серверным ядром поставляются модули и шаблоны классической витрины, написанные на нативном движке Melbis Shop. Однако система предоставляет полную свободу в выборе технологий для фронтенда. Вы можете управлять магазином через Windows-приложение Melbis Shop, а клиентскую витрину реализовать на любом удобном для вас фреймворке, например, на Laravel.
Важное архитектурное требование: Клиентское Windows-приложение и веб-витрина должны использовать единый математический аппарат для калькуляции заказов (скидки, налоги, опции). Поэтому при использовании Laravel вам потребуется настроить связь с базовыми модулями логики ядра Melbis Shop.
Для упрощения этой задачи мы подготовили базовый шаблон
магазина-витрины на Laravel.
🔗 Репозиторий: github.com/melbis/melbis-shop-laravel
Для корректной работы обеих систем серверное ядро магазина устанавливается в корневую директорию сайта, а Laravel — во вложенную папку (или настраивается соответствующий роутинг на уровне веб-сервера).
Установка ядра в корень:
composer create-project melbis/melbis-shop .Установка Laravel в директорию (например, /laravel):
cd laravel
composer require laravel/laravelЧтобы Laravel получил доступ к классам ядра Melbis, необходимо
обновить автозагрузчик. Откройте файл laravel/composer.json
и добавьте пространство имён Melbis в секцию psr-4, указав
путь к классам ядра:
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Melbis\\MelbisShop\\": "../core/class/"
}
}После сохранения файла перегенерируйте файлы автозагрузки Composer:
composer dump-autoloadФайл Melbis.php содержит вспомогательную функцию
MELBIS(), которая возвращает синглтон-экземпляр
Parser. Подключите его через секцию files в
composer.json:
"autoload": {
"psr-4": { ... },
"files": [
"app/Services/Melbis.php"
]
}После добавления снова выполните:
composer dump-autoloadСкопируйте пример файла окружения и сгенерируйте ключ приложения Laravel:
cp .env.example .env
php artisan key:generateПримечание: Убедитесь, что параметры подключения к базе
данных настроены корректно. Ядро Melbis динамически читает свою
конфигурацию напрямую из корневого файла config.json — этим
занимается метод loadConstants() внутри
MelbisLogic.
Данный проект использует гибридную архитектуру, которая объединяет классическую процедурную парадигму прямого управления Melbis Shop со строгой MVC-структурой Laravel.
App\Services\MelbisLogic)Это сердце интеграции. Сервис MelbisLogic действует как
обёртка-мост (Bridge), которая:
Parser
один раз через паттерн Singleton (статическое свойство
$parser).config.json и определяет
константы ядра через loadConstants().melbis_inc_logic через
MELBIS()->UnitInc(...).halt().call() для вызова нативных функций
Melbis (например,
$melbis->call('MELBIS_INC_LOGIC_order_calc', [$version])).Глобальная функция-хелпер MELBIS() (из файла
Melbis.php) предоставляет удобный доступ к уже
инициализированному Parser из любого места приложения:
// Melbis.php
if ( !function_exists('MELBIS') ) {
function MELBIS() {
return \App\Services\MelbisLogic::getParser();
}
}Важно:
MELBIS()бросает исключение, если вызвана до того, как был создан экземплярMelbisLogic. Убедитесь, чтоMelbisLogicинициализируется первым — например, через Service Provider или Dependency Injection в контроллере.
Контроллеры Laravel (такие как CartController) выполняют
исключительно роль маршрутизаторов. Они перехватывают HTTP-запросы,
получают MelbisLogic через Dependency Injection, управляют
сессиями Laravel и возвращают JSON-ответы или представления (views). Они
не содержат бизнес-логики или математических
расчётов.
Вся тяжёлая работа — запросы к БД, расчёты корзины, скидки,
многопоточность и обработка опций товара — выполняется внутри нативных
директорий Melbis /units/ и /core/. Это
гарантирует 100% математическую согласованность с десктопным
Windows-приложением.
Фронтенд построен с использованием шаблонизатора Laravel Blade и Bootstrap 5. Представления получают уже рассчитанные массивы данных из контроллеров и просто генерируют HTML, сохраняя слой отображения полностью изолированным от слоя обработки данных.
MelbisLogic)<?php
namespace App\Services;
use Exception;
use Melbis\MelbisShop\MySql;
use Melbis\MelbisShop\Parser;
class MelbisLogic
{
private static ?Parser $parser = null;
public function __construct()
{
$this->loadConstants();
$this->initializeMelbis();
MELBIS()->UnitInc('melbis_inc_logic');
}
private function loadConstants(): void
{
$configPath = base_path('../config.json');
if (file_exists($configPath)) {
$config = json_decode(file_get_contents($configPath), true) ?? [];
foreach ($config as $const => $value) {
if ( !defined($const) ) {
define($const, $value);
}
}
}
}
private function initializeMelbis(): void
{
if ( self::$parser !== null ) {
return;
}
$error_halt = [self::class, 'halt'];
$db = new MySql($error_halt);
$db->Connect(__FILE__, __LINE__);
self::$parser = new Parser($error_halt, $db);
}
public static function getParser(): Parser
{
if (self::$parser === null) {
throw new Exception("Melbis Shop not ready!");
}
return self::$parser;
}
public function call($functionName, $params = [])
{
if ( !is_callable($functionName) ) {
throw new Exception("Function core {$functionName} not found!");
}
return call_user_func_array($functionName, $params);
}
public static function halt($mType, $mFile, $mError, $mInfo = '')
{
$message = "Melbis Error [$mType] in $mFile: $mError";
if (!empty($mInfo)) {
$message .= " | Info: " . trim($mInfo);
}
throw new Exception($message);
}
}<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Store extends Model
{
protected $table = 'ms_store';
public $timestamps = false;
public function images()
{
return $this->hasMany(FilesStore::class, 'elem_id', 'id')
->where('kind_key', 'kDefault')
->orderBy('pos', 'asc');
}
public function topics()
{
return $this->belongsToMany(Topic::class, 'ms_topic_store', 'store_id', 'topic_id');
}
}CartController)<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Services\MelbisLogic;
class CartController extends Controller
{
// Добавить товар в корзину
public function add(Request $request, MelbisLogic $melbis)
{
$store_id = (int) $request->input('id');
$version = session('melbis_version');
if (!isset($version)) {
$version = $melbis->call('MELBIS_INC_LOGIC_order_create');
}
$version = $melbis->call('MELBIS_INC_LOGIC_order_goods_add', [$version, $store_id]);
$version = $melbis->call('MELBIS_INC_LOGIC_order_calc', [$version]);
session(['melbis_version' => $version]);
return response()->json(['result' => 'OK']);
}
// Получить список товаров для окна оформления
public function goods(Request $request)
{
$version = session('melbis_version');
if (!$version || empty($version['store'])) {
return view('store.partials.goods_empty')->render();
}
return view('store.partials.goods_list', [
'items' => $version['store']
])->render();
}
// Удалить товар из корзины
public function remove(Request $request, MelbisLogic $melbis)
{
$store_id = (int) $request->input('id');
$version = session('melbis_version');
if ($version) {
$version = $melbis->call('MELBIS_INC_LOGIC_order_goods_remove', [$version, $store_id]);
$version = $melbis->call('MELBIS_INC_LOGIC_order_calc', [$version]);
session(['melbis_version' => $version]);
}
return $this->goods($request);
}
// Оформить заказ и сохранить в БД
public function save(Request $request, MelbisLogic $melbis)
{
$version = session('melbis_version');
if (!$version || empty($version['store'])) {
return response()->json([
'result' => 'ERROR_EMPTY',
'message' => 'No items found in your cart!'
]);
}
if (isset($version['result']['value']) && $version['result']['value'] !== 'OK') {
return response()->json([
'result' => $version['result']['value'],
'message' => $version['result']['message']
]);
}
$result = $melbis->call('MELBIS_INC_LOGIC_order_edit', [$version]);
if ($result['value'] !== 'OK') {
return response()->json([
'result' => $result['value'],
'message' => $result['message']
]);
}
session()->forget('melbis_version');
return response()->json(['result' => 'OK']);
}
}