【问题标题】:How to persist data objects in Laravel/Livewire component?如何在 Laravel/Livewire 组件中持久化数据对象?
【发布时间】:2022-01-25 20:06:06
【问题描述】:

我们有一个链接到视图的 Livewire 组件,旨在显示一个包含超过 100,000 条记录的数据表,每个记录显示多个(最多 300 条)子记录,来自多个关系(主要是一对多和多对多)。经过长时间的优化和调试工作,为了获得 Eloquent 集合(以 15 个块分页)的繁重查询现在需要大约 15-20 秒才能完成。 该视图还旨在通过激活模式窗口来允许 CRUD 操作。 我们需要解决的主要问题是显示添加或编辑模式窗口的大延迟。此时,我们不需要再次运行数据表查询,因为基础记录尚未更改。主要查询应仅在初始加载时运行,或者在添加或更新记录后运行,此时分页数据表可能已更改。

这是组件的摘录,显示了相关代码:

public function render()
    {
        //******************** SECTION A *************** 
        //This is the eloquent collection we use to generate the data-table in the view
        //$q is a public property, binded to a input box, for searching purposes. $sortBy and $sortAsc are used to order the data-table.  
        
        $fonos = Fono::select('id','cod','titulo','sello_cod','ritmo_cod','propiedad_cod')
            ->with(['propiedad:id,cod,propiedad','sello:id,cod,sello','ritmo:id,cod,ritmo','artistas:id,cod,artista','autores:id,cod,autor','operadores:id,cod,operador','isrcs:id,isrc']) //Eager loading relationships to reduce queries (only required fields)
            ->when( $this->q, function($query) {
                return $query->where(function( $query) {
                    $query->where('titulo', 'like', '%'.$this->q . '%')
                        ->orWhere('cod', 'like', '%' . $this->q . '%')
                        ->orWhere('isrc', 'like', '%' . $this->q . '%')
                        ->orWhere('famoso_por', 'like', '%' . $this->q . '%')
                        ->orWhereHas('sello', function ( $query) {
                            $query->where('sello', 'like', '%' . $this->q . '%');})
                        ->orWhereHas('autores', function ( $query) {
                            $query->where('cod', 'like', '%' . $this->q . '%');}) 
                        ->orWhereHas('autores', function ( $query) {
                            $query->where('autor', 'like', '%' . $this->q . '%');})
                        ->orWhereHas('artistas', function ( $query) {
                            $query->where('cod', 'like', '%' . $this->q . '%');})            
                        ->orWhereHas('artistas', function ( $query) {
                            $query->where('artista', 'like', '%' . $this->q . '%');}) 
                        ->orWhereHas('operadores', function ( $query) {
                            $query->where('cod', 'like', '%' . $this->q . '%');})
                        ->orWhereHas('operadores', function ( $query) {
                            $query->where('operador', 'like', '%' . $this->q . '%');})
                        ->orWhereHas('isrcs', function ( $query) {
                            $query->where('isrc', 'like', '%' . $this->q . '%');})          
                        ;
                });
            })
            ->orderBy($this->sortBy, $this->sortAsc ? 'ASC' : 'DESC')->paginate(15);

        //******************** SECTION B *************** 
        //Here we load other Eloquent models (some with thousands of records), that are used to populate several selects (drop-down lists), that are visible when the modal window is activated to add or edit a record. 

        $tipos_fonos = TipoFono::select('cod','tipo_fono')->get();
        $sellos = Sello::select('cod','sello')->get();
        $ritmos = Ritmo::select('cod','ritmo')->get();
        $generos = Genero::select('cod','genero')->get();
        $idiomas = Idioma::select('id','idioma')->get();
        $propiedades = Propiedad::select('cod','propiedad')->get();
        $territorios = Territorio::select('cod','territorio')->get();
        $clases_artistas = ClaseArtista::select('id','clase_artista')->get();
        $artistasLista = Artista::select('cod','artista')->get();
        $papelesLista = PapelArtista::select('id','papel_artista')->get();
        $autoresLista = AutorFono::select('cod','autor')->get();
        $operadoresLista = Operador::select('cod','operador')->get();
        $tipos_autores = TipoAutor::select('id','cod','tipo_autor')->get();

        //******************** SECTION C ***************   
        //Finally, we pass the objects to the view

        return view('livewire.admin.fonos', [
            'fonos' => $fonos,
            'tipos_fonos' => $tipos_fonos,
            'sellos' => $sellos,
            'ritmos' => $ritmos,
            'generos' => $generos,
            'idiomas' => $idiomas,
            'propiedades' => $propiedades,
            'territorios' => $territorios,
            'clases_artistas' => $clases_artistas,
            'artistasLista'  => $artistasLista,
            'papelesLista'  => $papelesLista,
            'tipos_autores'  => $tipos_autores,
            'autoresLista'  => $autoresLista,
            'operadoresLista'  => $operadoresLista,
        ]);
    }

显然,我们需要一种方法来避免不必要的查询,但我们发现了实现该目标的重要限制。例如:

  1. 我们正在尝试在内存中存储用于在初始加载时填充选择元素的 Eloquent 模型(集合),因为该数据在生命周期中不会更改。因此,我们将这些查询移至 mount 方法,例如:

    public function mount()
     {
         // This runs once, like a constructor class  
         //Load all models for select drop-downs
         //all the variables are declared as public properties and also included in the $rules array (i.e. 'lw_generos.*.cod' => 'nullable')
    
         $this->lw_tipos_fonos = TipoFono::select('cod','tipo_fono')->get();
         $this->lw_sellos = Sello::select('cod','sello')->get();
         $this->lw_ritmos = Ritmo::select('cod','ritmo')->get();
         $this->lw_generos = Genero::select('cod','genero')->get();
         $this->lw_idiomas = Idioma::select('id','idioma')->get();
         $this->lw_propiedades = Propiedad::select('cod','propiedad')->get();
         $this->lw_territorios = Territorio::select('cod','territorio')->get();
         $this->lw_clases_artistas = ClaseArtista::select('id','clase_artista')->get();
         $this->lw_artistasLista = Artista::select('cod','artista')->get();
         $this->lw_papelesLista = PapelArtista::select('id','papel_artista')->get();
         $this->lw_autoresLista = AutorFono::select('cod','autor')->get();
         $this->lw_operadoresLista = Operador::select('cod','operador')->get();
         $this->lw_tipos_autores = TipoAutor::select('id','cod','tipo_autor')->get();
    
     }
    

    所以,为了和这个修改保持一致,我们改变了render方法的C部分:

    return view('livewire.admin.fonos', [
        'fonos' => $fonos,
        'tipos_fonos' => $this->lw_tipos_fonos,
        'sellos' => $this->lw_sellos,
        'ritmos' => $this->lw_ritmos,
        'generos' => $this->lw_generos,
        'idiomas' => $this->lw_idiomas,
        'propiedades' => $this->lw_propiedades,
        'territorios' => $this->lw_territorios,
        'clases_artistas' => $this->lw_clases_artistas,
        'artistasLista' => $this->lw_artistasLista,
        'papelesLista' => $this->lw_papelesLista,
        'autoresLista' => $this->lw_autoresLista,
        'operadoresLista' => $this->lw_operadoresLista,
        'tipos_autores' => $this->lw_tipos_autores,
    
    ]);
    

    这种方法的问题在于它会产生错误:

    Symfony\Component\ErrorHandler\Error\FatalError 超过 30 秒的最大执行时间 Symfony\Component\ErrorHandler\Error\FatalError::__construct 供应商/laravel/framework/src/Illuminate/Collections/Arr.php:115

    注意:我们尝试在视图中直接使用 Eloquent 集合的公共变量,而不显式传递它们,但结果是相同的。我们还考虑过将属性声明为私有或受保护,但数据不会保留(Livewire 文档指出私有或受保护属性无法在组件更新后继续存在)。

  2. 我们试图避免在视图渲染期间发生的不必要的查询。 $fonos Eloquent 集合应仅在数据更改(以模态形式完成的修改)或使用搜索输入时更新。在其他情况下,集合应该作为对象存储在内存中,准备好传递给视图。但实现该目标存在障碍:首先,雄辩的分页集合目前不能存储为 Livewire 属性(仅允许与 JavaScript 兼容的数据)。其次,即使我们可以将集合分配给公共属性,我们也会得到与前面所述相同的错误。

我们正在使用 Laravel 8.28.1 和 Livewire 2.3.18。

【问题讨论】:

  • 您需要在单个查询中改进您的查询或将其放在缓存中。现在您至少要打开 15 个与数据库的连接,这需要很大的时间成本。将 B 部分的大部分结果移动到缓存中。

标签: php laravel laravel-livewire


【解决方案1】:

我们通过使用 Livewire 计算属性和缓存查询结果解决了这个问题。例如:

public function getBancosProperty(){
        /* Caching */
        $expire = Carbon::now()->addMinutes(10);
        $bancos = Cache::remember('bancos', $expire, function() {
            return Banco::select('cod','banco')->orderBy('banco')->get();
        });
        return $bancos;
}

希望这对其他人有所帮助。

问候。

【讨论】:

  • 你试过在 mount() 方法上创建公共变量 $bancos 吗? ,唯一的缺点是这个变量的数据是 Javascript 可见的。如果您的银行列表显示敏感信息,请不要使用此解决方案。
猜你喜欢
  • 2020-12-31
  • 1970-01-01
  • 1970-01-01
  • 2022-11-28
  • 1970-01-01
  • 2021-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多