在 Laravel 4 中缓存视图输出

Posted

技术标签:

【中文标题】在 Laravel 4 中缓存视图输出【英文标题】:Caching View Output in Laravel 4 【发布时间】:2013-07-01 20:40:09 【问题描述】:

我知道 Blade 已经为所有刀片视图缓存了编译后的 php,但我想更进一步。我正在处理的网站被模块化为组件视图,然后在默认控制器中拼凑在一起。每个“小部件”都有自己的视图,很少更改内容(除了一些经常更新的内容)。所以,我想缓存这些很少变化的视图的 html 输出,以防止在每次页面加载时对它们进行评估。

在 Laravel 3 中,我们可以这样做 (credit Laravel forums):

Event::listen(View::loader, function($bundle, $view)

  return Cache::get($bundle.'::'.$view, View::file($bundle, $view, 
                                                  Bundle::path($bundle).'view'));
);

不幸的是,View::loader 在 Laravel 4 中完全消失了。当我深入研究 \Illuminate\View\View\Illuminate\View\Environment 时,我发现每个视图都会调度一个名为 "composing: view_name" 的事件。监听此事件会在每次视图渲染时提供视图名称和传递给它的数据,但是从回调返回的效果与在 Laravel 3 中的效果不同:

Event::listen('composing: *', function($view) 
  if(!in_array($view->getName(), Config::get('view.alwaysFresh'))) 
    // Hacky way of removing data that we didn't pass in
    // that have nasty cyclic references (like __env, app, and errors)
    $passedData = array_diff_key($view->getData(), $view->getEnvironment()
                                                                  ->getShared());

    return Cache::forever($view->getName() . json_encode($passedData), function() 
      return 'test view data -- this should appear in the browser';
    );
, 99);

以上并没有规避正常的视图包括和渲染过程。

那么如何绕过正常的视图渲染并从这个组合事件中返回缓存的内容呢?目前在 Laravel 中是否有可能没有一些丑陋的骇客?

【问题讨论】:

请问您是否这样做是为了避免重新创建视图数据?即使视图结果本身保存在缓存中,您仍然需要访问数据库来创建视图吗?你可能有更好的运气缓存数据库命中等的结果。 【参考方案1】:

又快又脏

好吧,我相信您知道,一种选择是在渲染视图时将项目缓存在控制器中。我怀疑您不想这样做,因为从长远来看,它的可维护性较差。

更易于维护的(?)方法

但是,如果视图加载器/渲染器没有在您想要的位置触发事件,您可以创建一个。因为 Laravel 4 中的每个包/库都是在 App 容器中设置的,所以您实际上可以将 View 库替换为您自己的。

我会采取的步骤是:

    创建库/包。目标是创建一个扩展 Laravel 视图逻辑的类。看过之后,你可能想扩展this one - 这是View 门面 如果您用自己的方式扩展了 View 外观(也就是说,如果我在步骤 1 中对文件的假设是正确的),那么您只需将 app/config/app.php 中的 alias for View 替换为您自己的外观。

编辑-我玩了一下。虽然我不一定同意缓存视图结果,而不是缓存 sql 查询或“更重的提升”,但这是我在 Laravel 4 中的做法

Laravel 4 中的视图渲染不会触发让我们缓存视图结果的事件。以下是我添加该功能以缓存视图结果的方式。

您可能需要考虑缓存视图结果的后果。例如,这并没有绕过与数据库对话以获取视图所需数据的艰苦工作。无论如何,这很好地概述了扩展或替换核心项目。

首先,创建一个包并设置它的自动加载。我将使用命名空间Fideloper\View。它在composer.json 中的自动加载看起来像这样:

"autoload": 
    "classmap": [
        "app/commands",
        "app/controllers",
        "app/models",
        "app/database/migrations",
        "app/database/seeds",
        "app/tests/TestCase.php"
    ],
    "psr-0": 
        "Fideloper": "app/"
    
,

接下来,创建一个类来替换 View 外观。在我们的例子中,这意味着我们将扩展 Illuminate\View\Environment。

在这个类中,我们将获取视图渲染的结果并添加一些逻辑来缓存(或不缓存)它。这里是Fideloper/View/Environment.php

<?php namespace Fideloper\View;

use Illuminate\View\Environment as BaseEnvironment;
use Illuminate\View\View;

class Environment extends BaseEnvironment 

    /**
     * Get a evaluated view contents for the given view.
     *
     * @param  string  $view
     * @param  array   $data
     * @param  array   $mergeData
     * @return \Illuminate\View\View
     */
    public function make($view, $data = array(), $mergeData = array())
    
        $path = $this->finder->find($view);

        $data = array_merge($mergeData, $this->parseData($data));

        $newView = new View($this, $this->getEngineFromPath($path), $view, $path, $data);

        // Cache Logic Here

        return $newView;
    


所以,这就是您的大部分工作所在 - 填写 // Cache Logic Here。但是,我们还有一些工作要做。

接下来,我们需要设置新的Environment 类以作为外观。我有一篇关于 creating Laravel facades 的博文。以下是在这种情况下如何实现这一点:

为我们的新环境创建外观。我们将在代码中将其命名为fideloper.view

<?php namespace Fideloper\View;

use Illuminate\Support\Facades\Facade;

class ViewFacade extends Facade 

    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()  return 'fideloper.view'; 


然后,创建 Service Provider,它会告诉 Laravel 在调用 fideloper.view 时要创建什么。请注意,这需要模仿 Illuminate\View\ViewServiceProvider 的功能来创建扩展的 Environment 类。

<?php namespace Fideloper\View;

use Illuminate\Support\ServiceProvider;

class ViewServiceProvider extends ServiceProvider 

    public function register()
    
        $this->app['fideloper.view'] = $this->app->share(function($app)
        
            // Next we need to grab the engine resolver instance that will be used by the
            // environment. The resolver will be used by an environment to get each of
            // the various engine implementations such as plain PHP or Blade engine.
            $resolver = $app['view.engine.resolver'];

            $finder = $app['view.finder'];

            $env = new Environment($resolver, $finder, $app['events']);

            // We will also set the container instance on this view environment since the
            // view composers may be classes registered in the container, which allows
            // for great testable, flexible composers for the application developer.
            $env->setContainer($app);

            $env->share('app', $app);

            return $env;
        );
    


最后,我们需要把这一切联系起来,告诉 Laravel 加载我们的服务提供者并用我们自己的替换 Illuminate 的 View 外观。编辑app/config/app.php

添加服务提供者:

'providers' => array(

    // Other providers

    'Fideloper\View\ViewServiceProvider',

),

用我们自己的替换 View 外观:

'aliases' => array(

    // Other Aliases

    //'View'            => 'Illuminate\Support\Facades\View',
    'View'            => 'Fideloper\View\ViewFacade',

),

然后您就可以在View::make() 方法中使用您希望的任何逻辑!

终于

值得注意的是,每个 Web 请求都需要在多个“请求”中加载一些模式。例如,Symfony 让你define controllers as servers。 Zend 有(有?)一个动作栈的概念,它让你

...有效地帮助您创建一个 [控制器] 操作队列以在请求期间执行。

也许您想在 Laravel 中探索这种可能性,并缓存这些“动作”的结果(而不是直接缓存视图)。

只是一个想法,不是推荐。

【讨论】:

+1 获取关于 ServiceProviders 和 Facades 的非常详细的指南(如果可以的话,我会 +2 或 +3!)。我一直在避免阅读有关它们的信息,但是您以其他方式说服了我。这是使用它们的一个很好的例子,也是扩展 Laravel 功能的一个很好的起点。谢谢! 回答你的问题,是的,我正在缓存数据库请求,因此缓存视图输出可能是不必要的微优化。但是,这个过程对于扩展 Laravel 功能来说是很好的了解。 我只是想澄清一些事情。只要您有一种方法可以检查缓存的 HTML 的缓存状态和时间戳,视图缓存就可以解决与数据库通信的需要。在您的控制器方法中,您只需检查方法顶部的缓存状态,然后再从模型中调用任何内容。如果缓存状态仍然有效,跳过所有的数据库内容,只返回 HTML。如果缓存状态不再有效,则执行正常请求并重新缓存结果。例如,这就是您使用 Smarty 的方式。 它也适用于可能进行大量渲染的文件。由于所有的部分、循环等,我有一个特殊的视图在新渲染上需要大约 100 毫秒。而且它永远不会(几乎永远不会)改变。轻松缓存并关闭该页面的 100 毫秒加载时间:)【参考方案2】:

在 Laravel 中有一个用于缓存视图/部件的库(不仅如此) - Flatten。

它是一个强大的缓存系统,用于在运行时缓存页面。它的作用很简单:你告诉他要缓存哪个页面,什么时候刷新缓存,然后 Flatten 从那里处理所有事情。它会悄悄地将您的页面扁平化为纯 HTML 并存储它们。如果用户访问一个已经扁平化的页面,那么所有的 PHP 都会被劫持以显示一个简单的 HTML 页面。这将为您的应用程序的速度提供重要的提升,因为您的页面缓存仅在其显示的数据发生更改时才会刷新。

通过artisan flatten:build 命令缓存应用程序中的所有授权页面。它会爬取你的应用程序并从一个页面到另一个页面,缓存你允许他访问的所有页面。

冲洗

有时您可能想要刷新特定页面或模式。例如,如果您缓存用户的配置文件,您可能希望在用户编辑其信息时刷新这些配置文件。您可以通过以下方法做到这一点:

// Manual flushing
Flatten::flushAll();
Flatten::flushPattern('users/.+');
Flatten::flushUrl('http://localhost/users/taylorotwell');

// Flushing via an UrlGenerator
Flatten::flushRoute('user', 'taylorotwell');
Flatten::flushAction('UsersController@user', 'taylorotwell');

// Flushing template sections (see below)
Flatten::flushSection('articles');

链接到 - https://github.com/Anahkiasen/flatten

【讨论】:

以上是关于在 Laravel 4 中缓存视图输出的主要内容,如果未能解决你的问题,请参考以下文章

laravel compact的用法

1+X web中级 Laravel学习笔记——blade模版

laravel框架总结 -- blade模板引擎

Laravel Redis 缓存

Laravel(4.1.24)将查询输出传递给视图时数组到字符串的转换

laravel框架(blade模板引擎)