За последние 24 часа нас посетили 16577 программистов и 1625 роботов. Сейчас ищут 1322 программиста ...

Laravel, заставить работать hasOneJson

Тема в разделе "PHP Free-Lance", создана пользователем lordconst, 5 сен 2024.

  1. lordconst

    lordconst Активный пользователь

    С нами с:
    7 дек 2019
    Сообщения:
    154
    Симпатии:
    1
    Решил делать свой пет-проект для тренировки разных задач в контексте геймдева. Кратко - браузерная онлайн-игра с ролевой системой. Дошла очередь до боя между двумя игроками на основе их характеристик.
    СУБД MySQL. Результаты боя храню в таблице fights, где есть таблица fighters (тип json). В этой таблице шаблонно сохраняются значения:
    {
    "attacker_id": 1,
    "defender_id": 3
    }
    Иными словами айди атакующего и айди защищающегося. Далее, пытаюсь получить объект с заранее прописанным отношением модели Fight к Users по attacker_id (по логике чтобы получить данные атакующего).
    Гуглил. Выяснил, что Laravel не умеет работать с hasOne, если это json-поле. Для решения проблемы на stackoverflow рекомендовали пакет https://github.com/staudenmeir/eloquent-json-relations
    Установил его в проект, сделал отношение hasOneJson как в документации, но оно не работает.

    Что нужно сделать? Разобраться, почему не работает отношение hasOneJson из вышеуказанного пакета, и показать как правильно нужно сделать (что и где изменить и на какую строку чтобы заработало); либо решить это средствами самого Laravel (на случай, если я недостаточно глубоко копнул и если фреймворк на самом деле может прогружать связи через json-объект и конкретное поле из него). Приложу скриншот ошибки и код. Если надо, поделюсь архивом проекта (гита нет).
    Пишите цены за ваше время.
    P.S. hasOneJson(User::class, 'id', 'attacker_id'); пробовал разные варианты с последним ключом. fights->attacker_id, fights['attacker_id'] и прочие. Не помогает. Ощущение, будто этого пакета нет в проекте, хотя в папке vendor я его вижу.
    PHP:
    1. class Fight extends Model
    2. {
    3.     use HasFactory;
    4.     use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;
    5.  
    6.     public function attacker(): \Staudenmeir\EloquentJsonRelations\Relations\HasOneJson
    7.     {
    8.         return $this->hasOneJson(User::class, 'id', 'attacker_id');
    9.     }
    10.  
    11.     /**
    12.      * The attributes that are mass assignable.
    13.      *
    14.      * @var array
    15.      */
    16.     protected $fillable = [
    17.         'fighters', 'results', 'rounds'
    18.     ];
    19.  
    20.     /**
    21.      * The attributes that should be cast to native types.
    22.      *
    23.      * @var array
    24.      */
    25.     protected $casts = [
    26.         'fighters' => 'json',
    27.         'results' => 'array',
    28.         'rounds' => 'array',
    29.     ];
    30. }
    PHP:
    1. public function getFightData($id) {
    2.         $fightData = Fight::with(['attacker'])->find($id);
    3.         dd($fightData);
    4.         return view('fightData', compact(['fightData', 'attacker', 'defender']));
    5.     }
    --- Добавлено ---
    Добавлю интересный момент. Если в методе getFightData($id) сделать так:
    PHP:
    1. public function getFightData($id) {
    2.         $fightData = Fight::find($id);
    3.         dd($fightData->fighters['attacker_id']);
    4.         return view('fightData', compact(['fightData', 'attacker', 'defender']));
    5.     }
    то Laravel выдаст конкретное число (айдишник), как и задумано логикой
     

    Вложения:

  2. ADSoft

    ADSoft Старожил

    С нами с:
    12 мар 2007
    Сообщения:
    3.850
    Симпатии:
    745
    Адрес:
    Татарстан
    1.
    приведите все же пример? а то не очень понятно таблица в таблице?
    Если предположить что fighters это поле, а не таблица... то скорее всего нужно
    Код (Text):
    1. return $this->hasOneJson(User::class, 'id', 'fighters->attacker_id');
     
  3. lordconst

    lordconst Активный пользователь

    С нами с:
    7 дек 2019
    Сообщения:
    154
    Симпатии:
    1
    Прилагаю скриншот. fights это таблица, fighters это участники боя, results это результат боя и rounds это лог боя (удар за ударом между игроками)
     

    Вложения:

  4. lordconst

    lordconst Активный пользователь

    С нами с:
    7 дек 2019
    Сообщения:
    154
    Симпатии:
    1
    Как вы предложили уже пробовал. Не удается, ошибка Call to undefined relationship [attacker] on model [App\Models\Fight].
    Исходя из текста ошибки понимаю что отношение некорректное. Но ведь я установил пакет выше. Почему laravel не видит его?
     
  5. gzhegow

    gzhegow Новичок

    С нами с:
    28 апр 2024
    Сообщения:
    16
    Симпатии:
    0
    Не стоит так делать. Понятно, что очень хочется, но может всё-таки не надо?

    Реляционные СУБД предполагают что "большие" пачки данных хранятся в вертикальных узких но очень высоких таблицах (many-to-many связи, справочники а ля wordpress postmeta) - то есть любой "мусор" который может когда-нибудь пригодится, но запрашивается за скрипт 1-2 раза, а часто требующиеся данные, требующие скоростного поиска и поиска по зависимостям - в отдельных столбцах. Отдельные стоблцы в интернет магазинах можно еще ускорить добавив фильтрам товаров и их значениям номера и закодировав их в числа, что позволило бы выбирать без условий вида "если это и это и это", а если "код фильтра по этому товару равен 0123012300123123" - что еще быстрее (аналогия - добавляем товару тег и ищем все товары по одному тегу, что быстрее, чем проверять все условия, чтобы тег определить)

    То есть совершенно нормально хранить в таблице "дерево постов" колонку "уровень вложенности" и последний из уровней отдельно хранить в таблице "постов" убедившись, что она обновляется ("кэшируется"). Ибо выбирать по этой колонке не осуществляя подключение к другим местам хранения будет быстрее.

    Важно понимать, что JOIN быстрее не-JOIN, только тогда когда на уровне задумки программы есть соединение между таблицами. Если можно сделать легкий и быстрый JOIN по числу или ID - его стоит и можно сделать. Но если требуется экстра скорость и мгновенность, то надо хитрить и делать задачу без соединений, отталкиваясь от того, что операции, подготовливающие эту экстра-скорость, делаются не каждую секунду, а например, раз в минуту, а вот операции выборки могут и несколько раз в секунду простреливать запросто.

    В вашем же случае, чтобы подсоединиться по JSON полю - придется всю таблицу делать json_decode() (а там к примеру миллион записей) - чтобы найти из этого миллиона одну строку, где поле равно например 3.

    JSON в данном случае это формат передачи между системами, и как вариант - хранения какого-то снимка или слепка, лога, дебага, информации которая требуется при работе с данными вручную, истории изменений. Реализовывать связи, опирающиеся на слепок - не очень хороший путь, сильно лучше будет вашу колонку при помещении данных копировать из JSON в обычную колонку, а те что уже есть - прогнать скриптом. Если переживаете, что пока скрипт работает появятся новые - запустите скрипт после того как подключите копирование из JSON в колонку, и добавьте в скрипте проверку "если уже есть - пропустить".

    Если ваши данные почти всегда имеют разное число столбцов и они настолько сложны, что описать для них фиксированный файл с полями почти невозможно - рассмотрите возможность подключить в проект колоночные БД типа Mongo или кликхауса, но по практике скажу, если вы не можете описать структуру данных вообще никак - то скорее всего вы либо пишите рассадку людей в концертном зале в 10 типах зон, с 10 типами зрителей, за 10ю типами (треугольный, круглый, квадратный, прямоугольный, еще какой-то) столиков - и даже тут посидеть с карандашом можно, чтобы найти общие моменты, либо делаете что-то не так. 90% задач "как делать поиск по JSON" связаны с "мне лень нормально делать", а еще точнее "менеджеру нет желания понимать, а мне стало быть плевать как оно будет работать".
     
    #5 gzhegow, 9 окт 2024
    Последнее редактирование: 9 окт 2024