【问题标题】:Laravel 5.4: How to get the same result without nesting one foreach inside another?Laravel 5.4:如何在不将一个 foreach 嵌套在另一个 foreach 中的情况下获得相同的结果?
【发布时间】:2018-04-05 14:20:13
【问题描述】:

我打算以某种语言获取新闻$newif($new_lang->lang_id == $lang_id),然后我查看与新闻相对应的图像表@foreach($data['images'] as $image),这是主要的@if($image->imageable_id == $new->id && $image->main == '1')

问题是嵌套 foreach 时,加载非常缓慢,因为它们包含 7,000 多条新闻和大约 70,000 张图片。

如何在不将一个 foreach 嵌套在另一个中的情况下获得相同的结果?

@foreach($new->langs as $new_lang)
@if($new_lang->lang_id == $lang_id)
    @foreach($data['images'] as $image)
        @if($image->imageable_id == $new->id && $image->main == '1')
            @php
                $mini = substr($image->path, 0, strrpos( $image->path, "/"));
                $name = substr($image->path, strrpos($image->path, '/') + 1);
                $image_mini = $mini.'/mini-'.$name;
            @endphp
            <div class="crop">
                <a href="{{ route('noticia', [$new->id, $new_lang->slug]) }}">{{ HTML::image(file_exists($image_mini)? $image_mini : $image->path, '', array('class' => 'img-responsive ancho_100')) }}</a>
            </div>
        @endif
    @endforeach
@endif
@endforeach

已编辑:添加了控制器和特征

这是控制器:

    use ListNoticias;

public function actualidad(Request $request)
{
    $data['section_id'] = explode(',', '1,2,3');

    $data['ruta'] = 'actualidad';
    $data['title'] = __('header.actualidad');
    $data['num'] = $request->num;
    $url = $request->url();

    $data = $this->listNoticias($data, $url);

    return view('web.actualidad.listado', compact('data'));
}

还有特点:

trait ListNoticias
{
public function listNoticias($data, $url)
{
    $now = date('Y-m-d');
    $time = date('H:i:s');

    (isset($data['num']))? $num = $data['num'] : $num = '15';

    $data['images'] = Image::where('imageable_type', 'App\Models\Noticia')->get();
    $data['sections']  = Section::all();

    $data['noticias'] = Noticia::with('langs')->where('date', '<', $now)
        ->where('active', '1')
        ->whereIn('section_id', $data['section_id'])
        ->orWhere('date', '=', $now)
        ->where('time', '<=', $time)
        ->where('active', '1')
        ->whereIn('section_id', $data['section_id'])
        ->orderBy('date', 'desc')
        ->orderBy('time', 'desc')
        ->get();

    $data['noticias-es'] = [];
    $data['noticias-en'] = [];
    $data['noticias-pt'] = [];

    foreach($data['noticias'] as $row){
        foreach($row->langs as $row_lang) {
            if ($row_lang->lang_id == '1') {
                $data['noticias-es'][] = $row;
            } elseif ($row_lang->lang_id == '2') {
                $data['noticias-en'][] = $row;
            } elseif ($row_lang->lang_id == '3') {
                $data['noticias-pt'][] = $row;
            } else null;

        }
    }

    // Manual paginate
    /*  Get current page form url e.x. &page=1
        Create a new Laravel collection from the array data
        Slice the collection to get the items to display in current page
        Create our paginator and pass it to the view
        set url path for generated links
    */

    $currentPage = LengthAwarePaginator::resolveCurrentPage();

    // ES
    $itemCollection = collect($data['noticias-es']);
    $currentPageItems = $itemCollection->slice(($currentPage * $num) - $num, $num)->all();
    $data['noticias-es'] = new LengthAwarePaginator($currentPageItems , count($itemCollection), $num);
    $data['noticias-es']->setPath($url);

    // EN
    $itemCollection = collect($data['noticias-en']);
    $currentPageItems = $itemCollection->slice(($currentPage * $num) - $num, $num)->all();
    $data['noticias-en'] = new LengthAwarePaginator($currentPageItems , count($itemCollection), $num);
    $data['noticias-en']->setPath($url);

    // PT
    $itemCollection = collect($data['noticias-pt']);
    $currentPageItems = $itemCollection->slice(($currentPage * $num) - $num, $num)->all();
    $data['noticias-pt'] = new LengthAwarePaginator($currentPageItems , count($itemCollection), $num);
    $data['noticias-pt']->setPath($url);

    return $data;

}
}

已编辑 2

第一个视图是部分视图,包含在此视图中,并且还有另外两个 foreach nestead。这是因为我需要三个显示记录。我已经评论了问题不需要的其他包含:

@foreach ($data['noticias-'.$lang]->chunk(3) as $chunk)

<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 pad_inf_2">

    @foreach($chunk as $key => $new)
        <div class="col-xs-12 col-sm-12 col-md-4 col-lg-4 pad_der_1">
            <article>
                //@include('web.index.partials.noticia_etiqueta')
                @include('web.actualidad.partials.noticia_image_listado')
                //@include('web.index.partials.noticia_date_section')
                //@include('web.index.partials.noticia_title')
            </article>
        </div>
    @endforeach

</div>
@endforeach

模型公告:

class Noticia extends Model
{
protected $fillable = ['section_id', 'active', 'date', 'time', 'author'];

public function images()
{
    return $this->morphMany('App\Models\Image', 'imageable');
}

public function tags()
{
    return $this->morphToMany('App\Models\Tag', 'taggable');
}

public function section()
{
    return $this->belongsTo('App\Models\Section');
}

public function langs()
{
    return $this->hasMany('App\Models\LangNoticia');
}

}

LangNoticia 模型:

class LangNoticia extends Model
{
protected $fillable = ['noticia_id', 'lang_id', 'title', 'lead', 'text', 'author_place', 'slug'];

public function noticia()
{
    return $this->belongsTo('App\Models\Noticia');
}
}

EDITED 3:在 abr

的建议下减少了 trait 中的代码

特征减少:

trait ListNoticias
{
public function listNoticias($data, $url)
{
    $lang = Session::get('lang');
    if($lang == 'en') $lang_id = '2';
    elseif($lang == 'pt') $lang_id = '3';
    else $lang_id = '1';

    $now = date('Y-m-d');
    $time = date('H:i:s');

    (isset($data['num']))? $num = $data['num'] : $num = '15';

    $data['sections']  = Section::all();
    $data['images'] = Image::where('imageable_type', 'App\Models\Noticia')->where('main', true)->get();

    $noticias = Noticia::with('langs')
        ->where('date', '<', $now)
        ->where('active', '1')
        ->whereIn('section_id', $data['section_id'])
        ->orWhere('date', '=', $now)
        ->where('time', '<=', $time)
        ->where('active', '1')
        ->whereIn('section_id', $data['section_id'])
        ->orderBy('date', 'desc')
        ->orderBy('time', 'desc')
        ->get();

    $data['noticias-'.$lang] = [];

    foreach($noticias as $row){
        foreach($row->langs as $row_lang) {
            if ($row_lang->lang_id == $lang_id) {
                $data['noticias-'.$lang][] = $row;
            }
        }
    }

    // Paginate
    /*  Get current page form url e.x. &page=1
        Create a new Laravel collection from the array data
        Slice the collection to get the items to display in current page
        Create our paginator and pass it to the view
        set url path for generated links
    */

    $currentPage = LengthAwarePaginator::resolveCurrentPage();

    $itemCollection = collect($data['noticias-'.$lang]);
    $currentPageItems = $itemCollection->slice(($currentPage * $num) - $num, $num)->all();
    $data['noticias-'.$lang] = new LengthAwarePaginator($currentPageItems , count($itemCollection), $num);
    $data['noticias-'.$lang]->setPath($url);

    return $data;

}
}

【问题讨论】:

  • 你能显示调用视图的控制器方法吗?
  • 是的,我已经更新了。
  • NoticiaImage之间有关系吗?
  • 是的,卡米洛。我已经再次更新,因为我看到信息丢失(编辑 2)
  • 一个Noticia 有几个images

标签: php laravel laravel-5 foreach


【解决方案1】:

只是分享一些想法。从检索Noticias的内容开始:

您真的需要所有这 3 种语言吗?未来还会有更多吗?您只想展示这些“公告”的一部分吗?

回答您应该提出的这些问题:

  1. 一种方法,即使添加另一种语言,您也不必更改函数;

  2. 一种提高查询性能的方法,因为获取 7k 条记录不同于记录 7k x 154 种语言

  3. 你真的需要全部 7k 吗?

例如,您可以将该查询拆分为多个(如果您确实需要所有语言)或使用 Eager Loading 语句中的高级函数对其进行验证。

Noticia::with(['langs' => function ($query) {
    //where this lang_id == 1, select it as whatever name you require
}])
.
.
.

其次,你确定你想要它们的原样吗?你没有用 orWhere 或 Where 过滤太多。如果您打算缩小搜索范围,我建议您在 where 语句中添加一个函数,就像上面显示的示例一样。 如果您想了解查询的全部内容,请写:

dd($data['noticias']->toSql());

如果您声称拥有 70k 多于 7k 条新闻,那么获取所有这些新闻的图片将会很繁重。您不需要真正从一开始就加载所有内容,您可以使用简单的-&gt;paginate(15); 来缩小范围,同时使用-&gt;skip(15);

【讨论】:

  • 谢谢 abr,我会按照你告诉我的,告诉你结果...如果我能做到的话 ;)
  • 嗨@abr 按照您的建议,通过获取会话变量的语言,我已经大大减少了代码。不会在超过 4 的语言中增长。我正在使用手动分页,因为我必须根据语言创建数组并且我不能使用 'paginate()' 我不知道该怎么做的是您提出的查询,因为我没有足够的知识。你能解释一下怎么做吗?我用 EDITED 3 中减少的特征更新了问题
  • 如果可以的话,留下你的表(或你需要信息的表)的基本结构和所需的 json 输出,我回家后会尽力帮助你,可以't atm
  • 感谢@abr 的安排!或多或少,我用你和卡米洛的建议解决了。我对即将发布的项目感到有些压力,所以现在我将保持这种状态。再次感谢。
【解决方案2】:

我认为您可以像这样访问新闻主图像:

$new_lang->noticia->images()->where('main', true)->first();

您可以在 LangNoticia 模型中定义 accessor 以方便调用。

public function getImageAttribute()
{
    return $this->noticia->images()->where('main', true)->first();
}

在您看来,您可以删除 foreach() 并改为调用访问器。

@foreach($new->langs as $new_lang)
    @if($new_lang->lang_id == $lang_id)
        @php
            $mini = substr($new_lang->image->path, 0, strrpos( $new_lang->image->path, "/"));
            $name = substr($new_lang->image->path, strrpos($new_lang->image->path, '/') + 1);
            $image_mini = $mini.'/mini-'.$name;
        @endphp
        <div class="crop">
            <a href="{{ route('noticia', [$new->id, $new_lang->slug]) }}">{{ HTML::image(file_exists($image_mini)? $image_mini : $new_lang->image->path, '', array('class' => 'img-responsive ancho_100')) }}</a>
        </div>
    @endif
@endforeach

现在您可以在您的 trait 中移除对 Image 模型的调用。

在查询Noticia 模型时使用eager loading 以减少查询次数。

Noticia::with(['images' => function($query) {
    $query->where('main', true);
}])->get();

【讨论】:

  • 谢谢卡米洛!这看起来很有趣。我以前没有使用过访问器,我会继续努力,我告诉你。
  • 我在定义或调用访问器时做错了。在LangNoticia我输入:public function getImageAttribute($value) { return ucfirst($value-&gt;noticia-&gt;images()-&gt;where('main', true)-&gt;first()); } 在视图中:@if($new_lang-&gt;image) 这给了我错误:尝试获取非对象的属性错误在LangNoticia
  • 在这种情况下,我认为您不需要 $value 参数。我用一个例子更新了我的答案。
  • 谢谢卡米洛!这很好用。我学到了新东西 :) 唯一的缺点是,对于这种特殊情况,它会进一步减慢负载。使用 foreach,它给了我 4 个查询的结果,使用访问器给了我 33 个查询的结果。因此,即使嵌套两个 foreach,最终也更快。我正在优化这个特征,如果我找到一个替代方案,也许可以替代创建三分之三列表的两个 foreachs 以减少加载时间。
  • 为了减少查询次数,您可以在查询Noticia 模型时使用eager loading
猜你喜欢
  • 2018-05-27
  • 1970-01-01
  • 2020-03-24
  • 1970-01-01
  • 1970-01-01
  • 2018-08-14
  • 2018-06-07
  • 2018-06-01
  • 2010-11-13
相关资源
最近更新 更多