【问题标题】:Laravel 5.2 view composer gets executed multiple timesLaravel 5.2 视图作曲家被多次执行
【发布时间】:2017-05-09 01:07:37
【问题描述】:

我正在处理的一个项目中遇到了一些(主要)性能问题,在记录了所有执行的查询后,我意识到其中许多查询被执行了多次,我无法找到问题的根源。

所有多次执行的查询都在我看来作曲家提供者。

这是我的视图作曲家的样子:

public function boot()
    {
        view()->composer('partials.sidebar', function ($view) {
            $currentCategory = $this->getCurrentCategory();
            $sidebarCategories = SidebarCategory::category($currentCategory)
                ->order()
                ->get();
            $view
                ->with('sidebarCategories', $sidebarCategories);
        });

        view()->composer('partials.footer', function ($view) {
            $footerLinks = FooterCategory::with('links.translations')->order()->get();
            $footerColumn1 = $footerLinks->filter(function ($value, $key) {
                return $value->column == 1;
            });
            $footerColumn2 = $footerLinks->filter(function ($value, $key) {
                return $value->column == 2;
            });
            $footerColumn3 = $footerLinks->filter(function ($value, $key) {
                return $value->column == 3;
            });
            $footerColumn4 = $footerLinks->filter(function ($value, $key) {
                return $value->column == 4;
            });

            $view
                ->with(compact('footerColumn1', 'footerColumn2', 'footerColumn3', 'footerColumn4'));
        });
}

这两个查询(Sidbar 和 Footer 类别)都会执行大约 6 次,即使每个部分只被调用一次。它们都在主视图中使用 @include('partialname') 调用。

我试过了:

if($view->offsetExists('sidebarCategory'))
    return;

但是 offsetExists 总是返回 false(即使在它调用了 5 次之后)。

知道为什么会发生这种情况以及我做错了什么吗?

编辑:

我已经意识到问题出在哪里。在我正在访问的页面上(执行多个查询的地方)有大约 404 个元素(主要是图像)。每次找不到文件时,都会引发新的 404 异常。每次抛出 404 异常时,都会执行 404 视图 => 意味着页脚/侧边栏查询也会执行(因为它们是 404 视图的一部分)。示例:http://imgur.com/a/RrmOD

所以后续问题是如何防止视图在不需要时被渲染(例如 404 是未找到的图像)。

这是我的路线中的一段代码,我认为这是发生这种情况的原因:

Route::get('{slug}', ['as' => 'findBySlug', 'uses' => function($slug) {
    if(\App\DynamicCategory::findBySlug($slug)->count() > 0)
        return App::make('App\Http\Controllers\GeneralController')->getDynamicCategoryIndex($slug);
    else if(\App\DynamicPage::noCategory()->findBySlug($slug)->count() > 0)
        return App::make('App\Http\Controllers\GeneralController')->getDynamicPage($slug);
    else
        abort(404);
}]);

PS:我知道这段代码非常未优化(因为它基本上执行了两次相同的查询,一次是查看项目是否存在,另一次实际上是在控制器中)。这是正在进行的工作,它在待办事项列表中。

编辑 2:

我提出了下一个解决方案,我愿意改进,因为这是一种有点 hack-ish 的方式(如果我添加更多文件夹,我还需要记住更新它)。我在公用文件夹中只有 3 个直接子文件夹:东西、文件和资源。

解决方案是在呈现异常时检查 url 的第一段(在文件 app/Exceptions/Handler.php 中),如果它与 3 个文件夹中的一个匹配,则返回不带视图的 404 响应:

public function render($request, Exception $e)
{
    $firstSegment = $request->segment(1);
    if(starts_with($firstSegment, 'files') || starts_with($firstSegment, 'something') || starts_with($firstSegment, 'resources')) {
        return response('Stran ne obstaja', 404);
    }

    return parent::render($request, $e);
}

提前致谢

【问题讨论】:

  • 在您的情况下,最好从错误页面中排除导致多次查询的内容。您可以通过在刀片主布局中使用 if/else 并在不应呈现内容时传递变量来轻松完成此操作。
  • 这不是一个简单的解决方案,因为我有大量的视图,并且在显示文件之前检查每个图像是否确实存在也会导致性能问题。
  • 不不,我的意思是你可以在调用 404.blade.php 时排除它。
  • 哦,我明白你的意思了。我会从 404 视图中删除 sidbar/footer。不幸的是,这不是我的决定,而是我的客户想要那样(阅读:我无法改变:/)
  • 也许,我说也许,您可以将这些查询的结果缓存在 APC 缓存中,并使用来自缓存而不是来自真实数据库的数据呈现 404 页面。

标签: php laravel laravel-5


【解决方案1】:

不要通过 laravel 路由文件请求并在 404 上提供空白图像

.htaccess 示例:

RewriteEngine On
RewriteCond %{REQUEST_URI} \.(jpg|jpeg|gif|png|ico)$ [NC]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .*$ /no_picture.png [L]

您可以在重定向到 index.php 的规则上方发布此内容

这是假设图片是从网站提供的,而不是通过流式数据库内容提供的。

基本上发生的事情是这样的:

  • 后端用户上传图片:
  • laravel 将图像存储在 /some/path/to/store/data/public/12a/4d8/458/12a4d8458.gif 中
  • laravel 将图像以 12a4d8458.gif 格式存储在您的数据库中
  • -------- 时间流逝------------
  • 访客请求页面。
  • 请求与文件不匹配。重定向到 index.php(根据 .htaccess)
  • 请求被发送到 index.php
  • laravel 构建页面。查找图片
  • laravel 组成完整的公共 url 路径
  • www.awesome.com/data/public/12a/4d8/458/12a4d8458.gif
  • 组合的 html 内容被推送给访问者
  • ---------------- 毫秒通过 ------------------------
  • 访问者请求图像 /data/public/12a/4d8/458/12a4d8458.gif
  • 请求匹配文件文件由 apache 提供服务
  • laravel 仍然幸福地不知道请求
  • ----------------- 没有经过一毫秒 -------------
  • 访问者请求图像 /data/public/4e8/d44/98f/4e8d4498f.gif
  • 请求与文件不匹配。重定向到 index.php(根据 .htaccess)
  • laravel 构建页面。找不到路线。
  • laravel 调用 404 例程
  • laravel 构建关联所有触发器的 404 页面
  • laravel 为用户提供 404 服务

因此,您在最后一步要做的就是防止图像请求甚至到达 laravel,方法是按照 .htaccess 规则的定义提供您自己的图像。 这样,您的网络服务器可以更快地响应丢失的图像。

【讨论】:

  • 有许多图像从 db 获取部分路径(路径看起来像这样 /files/pages/{id}/{image_name} - {} 来自 DB)如果这就是你的意思.但如果它有帮助,我在 /public 目录中只有 3 个子文件夹,名为“something”、“files”和“resources”。
  • 你能详细说明一下这里发生了什么。老实说,这对我来说就像胡言乱语,我对 .htaccess 了解不多。 github.com/laravel/laravel/blob/master/public/.htaccess 这是我当前的 .htaccess 的样子(默认)。这个放在哪里?
  • github.com/laravel/laravel/blob/master/public/.htaccess#L8我会更新我的答案以扩展流程解释
  • 我很确定这是最好的解决方案。但是在谷歌搜索之后,我意识到 .htaccess 是用于 apache 的,我使用的是 Homestead (nginx),更重要的是,生产服务器也将运行 nginx。还有什么想法吗?我认为 nginx 也必须有一个类似的配置文件。
【解决方案2】:

检查文件扩展名

而不是检查特定路径的 url 段,如果你说它只会是图像。因此,如果只需要具有特定扩展名的东西,您可以检查它并为那些返回更简单的 404 响应:)

/* \app\Exceptions\Handler.php */

public function render($request, Exception $exception)
{
    if ($exception instanceof NotFoundHttpException) {
        $ext = pathinfo($request->url(), PATHINFO_EXTENSION);
        if (in_array($ext, ['jpg', 'png'])) {
            return response('Nope');
        }
    }

    return parent::render($request, $exception);
}

缓存

刚刚意识到它发出了许多请求。可能是这样的,将结果缓存 1 分钟,这应该足够长到页面上的下一个 404 请求来获取缓存而不是运行查询:)

public function boot()
{
    // Sidebar
    $sidebarCategories = Cache::store('file')->remember('sidebarCategories', 1, function () {
        $currentCategory = $this->getCurrentCategory();
        return SidebarCategory::category($currentCategory)->order()->get();
    });

    view()->composer('partials.sidebar', function ($view) use ($sidebarCategories) {
        $view->with('sidebarCategories', $sidebarCategories);
    });


    // Footer
    $footerColumns = Cache::store('file')->remember('footerColumns', 1, function () {
        $footerLinks = FooterCategory::with('links.translations')->order()->get();
        return [
            1 => $footerLinks->filter(function ($value, $key) { return $value->column == 1; }),
            2 => $footerLinks->filter(function ($value, $key) { return $value->column == 2; }),
            3 => $footerLinks->filter(function ($value, $key) { return $value->column == 3; }),
            4 => $footerLinks->filter(function ($value, $key) { return $value->column == 4; }),
        ];
    });

    view()->composer('partials.footer', function ($view) use ($footerColumns) {
        $view->with([
            'footerColumn1' => $footerColumns[1],
            'footerColumn2' => $footerColumns[2],
            'footerColumn3' => $footerColumns[3],
            'footerColumn4' => $footerColumns[4],
        ]);
    });
}

【讨论】:

  • 不能解决我的问题:/我已经意识到为什么它会被执行多次。对于页面上抛出的每个 404,它执行一次。
  • 是的,意识到这一点,我已经将我的答案更新为另一个解决方案:)
  • 有人已经建议在 cmets 中进行缓存。毫无疑问,这是一个好主意,但我正在寻找一种方法来避免在丢失的页面实际上是一个文件(通常是图像)时一起呈现 404 视图。我实际上已经想出了一个可行的解决方案(在原始帖子中更新),但感觉有点骇人听闻。
  • 添加了一个替代解决方案,当你发现一个未找到的异常时检查文件扩展名:) 仍然不是最漂亮的,但我认为它应该比检查来自 url 段的路径需要更少的维护:)
【解决方案3】:

您可以在此处使用配置来解决视图组合的多次查询运行问题。比如下面的代码。

公共函数撰写(查看$view) {

if(!Config::has('composeVars')) 
{
    Config::set('composeVars') = [
      'data' => User::all();  
    ];
}

$view->with('*', Config::get('composeVars'));

}

【讨论】:

    猜你喜欢
    • 2014-12-01
    • 1970-01-01
    • 2016-10-30
    • 1970-01-01
    • 1970-01-01
    • 2017-01-06
    • 1970-01-01
    • 2015-06-14
    • 2016-04-26
    相关资源
    最近更新 更多