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,然后扩展我的所有模型自定义模型,并在该自定义模型中以某种方式添加此功能。问题是,我不知道如何(以及是否)可以添加此功能。
另一种方法是使用 Eloquent 关系 。所以像 $article->hasMany("translations")
这样的东西。这种情况下的问题是,为了指定这样的关系,一个外键是不够的,因为 translations
table 保存数据库中所有其他 table 的翻译。因此,一篇文章在 translations
table 中不仅由其 ID 表示,而且由 remote_table
、remote_id
和 remote_field
的组合表示。即使我能以某种方式建立这种关系,我可能仍然需要采取进一步的行动——翻译的内容确实会包含在结果中,但是 $article->title
仍然会有英语值,例如。
我在搜索时发现的第三种方法是使用访问器和修改器。虽然我对他们没有经验,但我看不出他们如何解决这个问题,因为这不是数据格式化的问题,而是用另一个 table 的数据替换它的问题。另外,他们并没有让我在获取数据后不得不采取进一步的行动。
我的赌注是扩展 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。
我想知道如何让 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,然后扩展我的所有模型自定义模型,并在该自定义模型中以某种方式添加此功能。问题是,我不知道如何(以及是否)可以添加此功能。
另一种方法是使用 Eloquent 关系 。所以像
$article->hasMany("translations")
这样的东西。这种情况下的问题是,为了指定这样的关系,一个外键是不够的,因为translations
table 保存数据库中所有其他 table 的翻译。因此,一篇文章在translations
table 中不仅由其 ID 表示,而且由remote_table
、remote_id
和remote_field
的组合表示。即使我能以某种方式建立这种关系,我可能仍然需要采取进一步的行动——翻译的内容确实会包含在结果中,但是$article->title
仍然会有英语值,例如。我在搜索时发现的第三种方法是使用访问器和修改器。虽然我对他们没有经验,但我看不出他们如何解决这个问题,因为这不是数据格式化的问题,而是用另一个 table 的数据替换它的问题。另外,他们并没有让我在获取数据后不得不采取进一步的行动。
我的赌注是扩展 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。