Laravel 5.2 Eloquent - 如何使用单独的翻译自动获取翻译后的内容 table

Laravel 5.2 Eloquent - how to automatically fetch translated content using separate translations table

我想知道如何让 Eloquent 自动为我从数据库中获取翻译后的内容,而无需我指定它。例如,不必 运行 像下面这样的东西:

$article = Article::someTranslationMethod("en")->first(); //to get the English version
$article = Article::someTranslationMethod("de")->first(); //to get the German version
$article = Article::someTranslationMethod()->first();     //to get the current locale version
//etc

我希望能够简单地 运行:

$article = Article::first();

并且已经翻译了结果 - 根据当前语言环境或其他。

详情:

我的数据库架构由文章 table 和翻译 table 组成。 (请原谅我的德语):

Articles
id  |           title            |                 body
------------------------------------------------------------------------
 1  |  How to drink your coffee  |  Hi, this is an article about coffee.

Translations
id  |  language  |  remote_table  |  remote_id  |  remote_field  |  value  
--------------------------------------------------------------------------------------------------------
 1  |        en  |      articles  |          1  |         title  |  How to drink your coffee
 2  |        de  |      articles  |          1  |         title  |  Wie sollte man seinen Kaffee trinken
 3  |        en  |      articles  |          1  |          body  |  Hi, this is an article about coffee.
 4  |        de  |      articles  |          1  |          body  |  Hallo, dieser Text geht um Kaffee.

因此,当区域设置为 "en" 时:$article->title 应该具有值 "How to drink your coffee",但是当区域设置为 "de" 时:$article->title 的值应该是 "Wie sollte man seinen Kaffee trinken".

过去使用 ActiveRecord,我能够通过 运行 在从数据库中获取结果后立即调用一个函数来简单地实现这一点。它会查看 translations table 的翻译,并用它们的翻译替换 $article 对象的属性。就是这样。每次我必须获取一篇文章时,我都会 运行 类似 $article = MyActiveRecord::FindFirst('articles'); 的东西,而且我相信结果已经包含翻译后的值,而无需采取任何进一步的行动来实现这一点 -这正是我所追求的。

但是使用 Laravel 和 Eloquent,这是不可能的了。至少,不是开箱即用的。 (即使是,那也意味着更改 Eloquent 的源代码——这不是一个选项)。因此,它必须手动实现。现在我可以想到三种可能的方法:

我的赌注是扩展 Eloquent\Model class,并以某种方式让它在那里发生。但是我不知道怎么办。

如有任何想法,我们将不胜感激。

我认为您可以通过将全局范围应用于所有 Article 查询来实现这一点。

打开您的 Article 模型,然后找到 public static function boot()(如果不存在则创建它)

public static function boot()
{
    parent::boot();

    static::addGlobalScope('transliterated', function(Builder $builder){
        return $builder->translate(app()->getLocale());
    });
}

这对您来说应该是开箱即用的,适用于针对 Article::

进行的所有查询

最终成功的解决方案是使用 Eloquent Event。我确实创建了一个扩展 Eloquent\Model 的自定义基础模型 class,我所做的是向它注册一个新的 Eloquent 事件,称为 "loaded",它被触发在创建模型实例之后。所以现在可以相应地操作结果,并且 returned 到我们已经翻译的应用程序。

这是我的自定义基础模型,我要在其中注册自定义 "loaded" 事件:

<?php

//app/CustomBaseModel.php

namespace App;

use Illuminate\Database\Eloquent\Model;

abstract class CustomBaseModel extends Model
{
    /**
     * Register a loaded model event with the dispatcher.
     *
     * @param  \Closure|string  $callback
     * @return void
     */
    public static function loaded($callback)
    {
        static::registerModelEvent('loaded', $callback);
    }

    /**
     * Get the observable event names.
     *
     * @return array
     */
    public function getObservableEvents()
    {
        return array_merge(parent::getObservableEvents(), array('loaded'));
    }

    /**
     * Create a new model instance that is existing.
     *
     * @param  array  $attributes
     * @param  $connection
     * @return \Illuminate\Database\Eloquent\Model|static
     */
    public function newFromBuilder($attributes = array(), $connection = NULL)
    {
        $instance = parent::newFromBuilder($attributes);

        $instance->fireModelEvent('loaded', false);

        return $instance;
    }
}//end class

然后我不得不告诉应用程序监听这个事件,所以我为此创建了一个新的 Service Provider

<?php

//app/Providers/TranslationServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;

class TranslationServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot(DispatcherContract $events)
    {
        $events->listen('eloquent.loaded: *', function ($ourModelInstance) {  // "*" is a wildcard matching all models, replace if you need to apply to only one model, for example "App\Article"

            //translation code goes here
            $ourModelInstance->translate();  //example
        }, 0);
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

我向应用程序注册了新的服务提供商:

<?php

//config/app.php

...

'providers' => [

     ...

     /*
      * Custom Service Providers...
      */

     App\Providers\TranslationServiceProvider::class,
],

...

最后,对于我们希望能够使用 "loaded" 事件的所有模型,我们从 CustomBaseModel class 而不是 Eloquent\Model 扩展它们:

<?php

//app/Article.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends CustomBaseModel
{
    //
}

瞧。每次我们加载一篇文章时,都会触发 "loaded" 事件,所以我们可以用它做任何我们想做的事,然后 return 它到应用程序。

旁注:

  • 有关 github 为什么此事件尚未包括在内的争论可以在 here
  • 中找到
  • 我的想法最初来自 this post