Laravel 5.2 Eloquent - 如何使用单独的翻译表

时间:2016-08-08 15:25:55

标签: php laravel eloquent laravel-5.2 multilingual

我想知道如何让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();

并且已经翻译了结果 - 根据当前的区域设置或其他任何内容。

详细信息:

我的数据库架构由一个Articles表和一个Translations表组成。 (请原谅我的德语):

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表中的翻译,并将$article对象的属性替换为其翻译。就是这样。每次我必须获取一篇文章时,我会运行$article = MyActiveRecord::FindFirst('articles');之类的东西,我相信结果已经保留了翻译的值,而我不需要采取任何进一步的行动来实现这一点 - 这正是我在追求。

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

  • 一种可能的方法是使用自定义基本模型扩展Eloquent \ Model类,然后让我的所有模型扩展自定义模型,并在该自定义模型中以某种方式添加此功能。问题是,我不知道如何(以及如果)我可以添加此功能。

  • 另一种方法是使用Eloquent 关系。像$article->hasMany("translations")这样的东西。在这种情况下的问题是,为了指定这样的关系,一个外键是不够的,因为translations表保存数据库中每个其他表的翻译。因此,translations表格中的文章不仅由其ID表示,还由remote_tableremote_idremote_field组合表示。即使我能以某种方式创建这种关系,我可能仍然需要采取进一步的行动 - 翻译的内容确实会包含在结果中,但$article->title仍然会有英文值。例如。

  • 我在搜索时发现的第三种方法是使用访问器和变更器。虽然我对它们没有经验,但我看不出它们如何解决这个问题,因为它不是数据格式化的问题,而是用另一个表中的数据替换它。此外,它们不会让我不必在获取数据后采取进一步行动。

我的下注是扩展Eloquent \ Model类,并以某种方式使它在那里发生。但我不知道如何。

任何想法都将受到高度赞赏。

2 个答案:

答案 0 :(得分:1)

我认为您可以通过应用于所有Article个查询的全局范围来实现此目的。

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

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

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

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

的所有查询

答案 1 :(得分:0)

最终解决问题的解决方案是使用Eloquent 事件。我确实创建了一个扩展Eloquent \ Model的自定义基础模型类,我所做的是向它注册一个新的Eloquent事件,称为" 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

然后我必须告诉应用程序听取此事件,因此我为此创建了一个新的服务提供商

<?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,
],

...

最后,对于我们希望能够使用&#34;加载&#34;的所有模型。 event with,我们从CustomBaseModel类扩展它们,而不是Eloquent \ Model 1:

<?php

//app/Article.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends CustomBaseModel
{
    //
}

瞧。每次我们加载文章时,&#34;加载&#34;事件被触发,因此我们可以随心所欲地使用它,然后将其返回给应用程序。

附注:

  • 关于github关于为什么还没有包含此事件的辩论可以找到here
  • 我最初从this post获得了这个想法。