Интеграция с Laravel

По умолчанию вместе с серверным ядром поставляются модули и шаблоны классической витрины, написанные на нативном движке Melbis Shop. Однако система предоставляет полную свободу в выборе технологий для фронтенда. Вы можете управлять магазином через Windows-приложение Melbis Shop, а клиентскую витрину реализовать на любом удобном для вас фреймворке, например, на Laravel.

Важное архитектурное требование: Клиентское Windows-приложение и веб-витрина должны использовать единый математический аппарат для калькуляции заказов (скидки, налоги, опции). Поэтому при использовании Laravel вам потребуется настроить связь с базовыми модулями логики ядра Melbis Shop.

Для упрощения этой задачи мы подготовили базовый шаблон магазина-витрины на Laravel.
🔗 Репозиторий: github.com/melbis/melbis-shop-laravel


1. Совместная установка

Для корректной работы обеих систем серверное ядро магазина устанавливается в корневую директорию сайта, а Laravel — во вложенную папку (или настраивается соответствующий роутинг на уровне веб-сервера).

Установка ядра в корень:

composer create-project melbis/melbis-shop .

Установка Laravel в директорию (например, /laravel):

cd laravel
composer require laravel/laravel

2. Настройка моста между Laravel и Melbis (The Bridge)

Шаг 1: Настройка автозагрузки (Autoloading)

Чтобы 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

Шаг 2: Подключение глобального хелпера

Файл Melbis.php содержит вспомогательную функцию MELBIS(), которая возвращает синглтон-экземпляр Parser. Подключите его через секцию files в composer.json:

"autoload": {
    "psr-4": { ... },
    "files": [
        "app/Services/Melbis.php"
    ]
}

После добавления снова выполните:

composer dump-autoload

Шаг 3: Настройка окружения (Environment)

Скопируйте пример файла окружения и сгенерируйте ключ приложения Laravel:

cp .env.example .env
php artisan key:generate

Примечание: Убедитесь, что параметры подключения к базе данных настроены корректно. Ядро Melbis динамически читает свою конфигурацию напрямую из корневого файла config.json — этим занимается метод loadConstants() внутри MelbisLogic.


3. Обзор архитектуры (Гибридный подход)

Данный проект использует гибридную архитектуру, которая объединяет классическую процедурную парадигму прямого управления Melbis Shop со строгой MVC-структурой Laravel.

1. Мост (App\Services\MelbisLogic)

Это сердце интеграции. Сервис MelbisLogic действует как обёртка-мост (Bridge), которая:

Глобальная функция-хелпер MELBIS() (из файла Melbis.php) предоставляет удобный доступ к уже инициализированному Parser из любого места приложения:

// Melbis.php
if ( !function_exists('MELBIS') ) {
    function MELBIS() {
        return \App\Services\MelbisLogic::getParser();
    }
}

Важно: MELBIS() бросает исключение, если вызвана до того, как был создан экземпляр MelbisLogic. Убедитесь, что MelbisLogic инициализируется первым — например, через Service Provider или Dependency Injection в контроллере.

2. Тонкие контроллеры (Thin Controllers)

Контроллеры Laravel (такие как CartController) выполняют исключительно роль маршрутизаторов. Они перехватывают HTTP-запросы, получают MelbisLogic через Dependency Injection, управляют сессиями Laravel и возвращают JSON-ответы или представления (views). Они не содержат бизнес-логики или математических расчётов.

3. Нативные модули (Ядро Melbis)

Вся тяжёлая работа — запросы к БД, расчёты корзины, скидки, многопоточность и обработка опций товара — выполняется внутри нативных директорий Melbis /units/ и /core/. Это гарантирует 100% математическую согласованность с десктопным Windows-приложением.

4. Представления (Dumb Views)

Фронтенд построен с использованием шаблонизатора Laravel Blade и Bootstrap 5. Представления получают уже рассчитанные массивы данных из контроллеров и просто генерируют HTML, сохраняя слой отображения полностью изолированным от слоя обработки данных.


4. Примеры кода

Сервис-мост (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);
    }
}

Использование моделей Eloquent для подключения к БД Melbis Shop

<?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']);
    }
}