Высокие накладные расходы на запросы с помощью Rainlab Translate для раскрывающегося меню

У меня есть модель под названием Area, которая содержит список имен областей, которые мне нужны для заполнения раскрывающегося списка. Список переводится с помощью плагина Rainlab Translate.

Если я просто сделаю прямое Area::lists(), то список не будет переведен. Однако, если я делаю Area::get()->lists(), то он переводится, но выполняется один запрос в таблице rainlab_translate_attributes для каждого отдельного элемента в раскрывающемся списке, что приводит к выполнению ~ 100 запросов и продолжительности запроса 1,5 с.

Модель

<?php namespace Namespace\PluginName\Models;

use Model;

class Area extends Model
{
    public $implement = ['RainLab.Translate.Behaviors.TranslatableModel'];

    public $translatable = ['name'];

    // .... 
}

Просмотреть

<div class="form-group {{ errors.first('location_id') ? 'has-error' }}">
    {{ form_label('area_id','Area') }}
    {{ form_select('area_id', {'': 'Select...'} + area, null, {'class': 'form-control', 'placeholder': 'Select...'}) }}
    <small class="text-danger" data-validate-for="area_id"></small>
</div>

Вариант компонента 1 (быстрый запрос, но элементы не переводятся)

public function areas() {
    return Area::lists('name','id');
}

Вариант компонента 2 (элементы переведены, но ~100 запросов и очень медленно)

public function areas() {
    return Area::get()->lists('name','id');
}

В других подобных ситуациях я бы добавил public $with = ['relation'], но в таблице rainlab_translate_attributes, похоже, нет модели, с которой я мог бы связать модель Area.

ОБНОВЛЕНИЕ

Я создал следующие функции в моей модели Area.php:

public static function listAreas()
{
    $areas = Cache::rememberForever("all:" . App::getLocale()  , function() {
        return self::
        whereNotNull('iso3166_2')
        ->get()
        ->toArray();
    });

    return  self::makeCollection( $areas ) ;
}

public static function makeCollection ( array $models = [] )
{
    return self::hydrate( $models );
}

... а затем в моем компоненте я пробовал:

$areas = Area::listAreas(); ‹-- это немедленно читает кэшированные данные

$areas->lists('name','id'); ‹-- это приводит к созданию нового запроса для каждого элемента в коллекции, вот пример одного запроса:

select * fromrainlab_translate_attributeswherelocale= 'th' andmodel_id= '1275' andmodel_type= 'Namespace\PluginName\Models\Area' limit 1

Я убедился, что App::getLocale() правильно установлено как th


person Joseph    schedule 30.01.2018    source источник
comment
вы рассматривали кеширование вашего запроса?   -  person Raja Khoury    schedule 04.02.2018
comment
К сожалению, кэширование запроса не поможет, потому что будет кэшироваться только исходный запрос (который в любом случае занимает всего 50 мс), а не последующие запросы, которые вызываются автоматически для обработки переводов.   -  person Joseph    schedule 21.02.2018
comment
На самом деле я кэширую модель, ее переведенные атрибуты и отношения. Я использую Redis, например, вы можете добавить префикс активной локали к ключу кеша и кэшировать несколько версий.   -  person Raja Khoury    schedule 21.02.2018
comment
Добавлен ответ, надеюсь, это поможет. Ваше здоровье   -  person Raja Khoury    schedule 21.02.2018
comment
попробуйте загрузить свою модель с отношением translations. protected $with = ['translations'];   -  person Natwar Singh    schedule 17.03.2021


Ответы (3)


У меня были те же требования, и я решил их с помощью кэширования. Если вы не хотите кэшировать свои запросы, игнорируйте этот ответ, но я думаю, вам следует его рассмотреть.

1) Убедитесь, что ваш RainLab Translator настроен, поэтому при использовании App::getLocale() возвращается активная локаль переводчика, а не Laravel.

2) Создайте метод в своей модели для внешнего использования. Цель состоит в том, чтобы кэшировать модель/отношения и переведенные атрибуты.

Например, AreaModel.php

public static function listAreas()
{
       $areas = Cache::tags([  'areas' ])
            ->rememberForever(  "all:" . App::getLocale()  , function() {
                return self::
                    with(['relation_model_name']) // Fetch the Relation
                    ->get()
                    ->toArray();
            });

    return  self::makeCollection( $areas ) ;
}

public static function makeCollection ( array $models = [] )
{
    return self::hydrate( $models );
}

а) Здесь мы кэшируем запрос с помощью ключа, включающего активную локаль.

б) Мы добавляем with для связанных моделей

c) Мы просто кэшируем всю коллекцию (No pluck/lists) и преобразовываем обратно в красноречивый экземпляр модели.

Преимущество заключается в том, что теперь в вашем компоненте Area::listAreas(); будут возвращаться кэшированные коллекции, и вы можете манипулировать ими, как и любым другим.

$areas = Area::listAreas(); // collection ( Area + Relation )

$dropdown = $areas->pluck('name', 'id'); // get Dropdown values for Areas...

Некоторым соображением является очистка кеша (удаление тега/ключа кеша) каждый раз, когда запись обновляется, добавляется или удаляется (модель + отношения).

Ниже приведены снимки экрана Redis Cache Store для Store Model и его реляционной модели Business Type;

Например, модель магазина - EN Например, модель магазина — ES

Обновление :

Во-первых, я прошу прощения за использование Redis в моем примере, предполагая, что все так делают. Исходный пост должен был быть больше сосредоточен на реализации. Пожалуйста, не копируйте/вставляйте код, как это делал я.

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

b) return self::whereNotNull('iso3166_2')->get()->lists('name','id') более чем достаточно для кэширования записей Areas.

c) В своем комментарии я использовал pluck, потому что lists устарел. pluck возвращает коллекцию — см. здесь и здесь

$areas = self::whereNotNull('iso3166_2')->pluck('name', 'id') ; // collection
$areas->toArray();

Я еще не пробовал кеширование на основе файлов с October и не уверен в его поведении по сравнению с Redis.

Опять же, некоторые соображения;

а) Пожалуйста, назовите свой ключ кеша чем-то уникальным и осмысленным, в моем посте all + locale был связан с тегом кеша areas . например areas.iso3166_2.locale (избегайте переопределений)

б) Добавить Cache::forget('key'); в ваших моделях afterSave и afterDelete методы

c) Если вы кэшируете связанные модели, также будьте осторожны, очищая кеш при их изменении.

person Raja Khoury    schedule 21.02.2018
comment
спасибо за это - у меня это частично работает, так как я могу вызвать $areas = Area::listAreas();, и он кэширует его в первый раз, а затем готовит его из кеша во второй раз. Я проверил, что также присутствуют правильные переведенные значения. Однако неожиданное поведение заключается в том, что если я запускаю $areas->pluck('name', 'id'); или $areas->lists('name', 'id');, он автоматически снова запрашивает таблицу перевода rainlab. Я не понимаю, почему это происходит, когда данные кэшируются. - person Joseph; 22.02.2018
comment
Вы уверены, что запрос связан с моделью области? Вы проверили, что не вызываете модель из другого кода? Можете ли вы опубликовать свой метод listAreas? - person Raja Khoury; 22.02.2018
comment
спасибо за ваш ответ - я обновил исходный вопрос, чтобы показать код, который я пробовал. Обратите внимание, что я использую кеш на основе файлов, а не Redis, поэтому мне пришлось удалить часть тегов. - person Joseph; 22.02.2018
comment
Что, если вы просто вернете self::whereNotNull('iso3166_2')-›pluck('name','id'): в своем методе и забудете о 'гидрате'. Я разместил его в своем ответе, но в этом случае это не обязательно это был пример. - person Raja Khoury; 22.02.2018
comment
Хорошо, это работает! Я должен был оставить get() и там, иначе перевод не состоялся. Итак, я сделал return self::whereNotNull('iso3166_2')->get()->lists('name','id') внутри Cache закрытия, и он отлично работает. Благодарю вас! Вы хотите обновить ответ, чтобы я мог отметить его правильно? - person Joseph; 22.02.2018
comment
@Joseph Рад, что это помогает - ответ обновлен. Вы действительно проделали большую часть работы. Удачи! - person Raja Khoury; 22.02.2018
comment
Спасибо за обновление ответа и добавление напоминания об очистке кеша на afterSave и afterDelete - теперь я отметил правильный ответ. - person Joseph; 23.02.2018

Вам нужно запустить JOIN вручную Я думаю, кажется, no functionality available for collection.

$locale = 'de';
$query = \HardikSatasiya\DemoTest\Models\Relation::query();

$query->select($query->getModel()->getTable() .'.*');
$query->addSelect('rainlab_translate_attributes.attribute_data');
$query->leftJoin('rainlab_translate_attributes', function($join) use ($locale, $query) {
    $join
        ->on(\Db::raw(\DbDongle::cast($query->getModel()->getQualifiedKeyName(), 'TEXT')), '=', 'rainlab_translate_attributes.model_id')
        ->where('rainlab_translate_attributes.model_type', '=', get_class($query->getModel()))
        ->where('rainlab_translate_attributes.locale', '=', $locale)
    ;
});

$data = $query->get();
$translatedArray = [];
foreach ($data as $value) {
    if(is_null($value->attribute_data)) {
        $translatedArray[$value->id] = $value->name;
    }
    else {
        $translations = json_decode($value->attribute_data);
        $translatedArray[$value->id] = $translations->name;
    }

}
dd($translatedArray);

может быть это поможет вам.

person Hardik Satasiya    schedule 30.01.2018
comment
Еще раз здравствуйте @Hardik Satasiya спасибо за ответ на мою проблему и извините за задержку с ответом. Ваше решение выглядит интересно, но я не знаю, где реализовать этот код, чтобы использовать его во внешнем интерфейсе. Принадлежит ли он компоненту или модели? - person Joseph; 21.02.2018
comment
Я думаю, вы можете написать этот код внутри public static function listAreas(){ ..here...} и вернуть переведенные параметры. Кэширование — это хорошо, но оно может быть полезно для статических данных, данные, которые могут быть изменены в какое-то время, могут создавать проблемы, иногда не всегда :) - person Hardik Satasiya; 23.02.2018
comment
Я думаю, что в данном конкретном случае (учитывая, что я просто имею дело со списком областей, которые изменятся только в том случае, если правительство переназначит названия районов) кэширование — лучший вариант. Однако ваше предложение будет очень ценным для других случаев, и я запишу его. Я также проголосовал за это. Большое спасибо за помощь @Hardik Satasiya! - person Joseph; 23.02.2018

Еще одна идея, которая может повлиять на скорость (если не на количество запросов) - индексирование свойства имени модели:

public $translatable = [
    ['name', 'index' => true]
];

Ссылка: https://github.com/rainlab/translate-plugin#indexed-attributes< /а>

person Eoler    schedule 31.01.2018
comment
Спасибо, но, к сожалению, добавление индекса не повлияло на скорость запросов. - person Joseph; 21.02.2018