构建 Laravel 翻译包 - 数据库驱动
发布时间 作者 Joe Dixon
在系列的上一篇文章中,我们讨论了如何处理缺失的翻译,这让我们离将该包的功能完善非常近。为了完成本系列的构建阶段,我们将讨论如何添加数据库驱动。
签署合同
正如您可能还记得本系列的早期内容,我们使用接口定义了文件驱动程序需要实现的所有方法才能正常运行。这样做的主要原因是,始终在包路线图中能够支持多个驱动程序。定义接口意味着我们有一个合同,所有新的驱动程序都可以依赖该合同来提供所需的功能。
迁移
为了启动数据库驱动程序,我们需要一些数据库表来存储我们的翻译。这不需要过于复杂 - 一个用于存储语言的表和另一个用于存储翻译的表就足够了。
// languages tablepublic function up(){ Schema::create(config('translation.database.languages_table'), function (Blueprint $table) { $table->increments('id'); $table->string('name')->nullable(); $table->string('language'); $table->timestamps(); });} // translations tablepublic function up(){ Schema::create(config('translation.database.translations_table'), function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('language_id'); $table->foreign('language_id')->references('id')->on(config('translation.database.languages_table')); $table->string('group')->nullable(); $table->text('key'); $table->text('value')->nullable(); $table->timestamps(); });}
我们使用包配置来决定迁移中表的名称。这允许包的用户命名他们自己的表,并避免任何潜在的冲突。
还值得注意的是翻译表上的 language_id
外键。这告诉我们每个翻译属于哪种语言。
还有一个名为 group
的可空字段,它将用于跟踪它是单个翻译还是组翻译。
最后,还有用于 key
和 value
的字段,顾名思义,它们存储每个翻译的键和值。
为了使迁移在用户运行 php artisan migrate
时自动运行,我们需要使用 loadMigrationsFrom
方法将它们加载到我们的服务提供程序中,并将新创建的迁移的路径传递进去。
public function boot(){ $this->loadMigrationsFrom(__DIR__.'/../database/migrations');}
模型
我们的模型也可以保持相对简单。鉴于我们允许用户定义他们自己的表名,每个模型都需要明确定义其关联的表。我们可以在模型的构造函数中通过将配置中设置的值赋值来做到这一点。
public function __construct(array $attributes = []){ parent::__construct($attributes); $this->table = config('translation.database.languages_table');}
最后,每个表都需要定义它与另一个表的关联关系
... class Langauge extends Model{ ... public function translations() { return $this->hasMany(Translation::class); }}
... class Translation extends Model{ ... public function language() { return $this->belongsTo(Language::class); }}
连接驱动程序
在定义了迁移和模型之后,我们现在可以开始构建数据库驱动程序实现。为此,我们将创建一个名为 Database
的新类,并实现驱动程序接口。我们还将扩展 Translation
抽象类,该类包含所有驱动程序实现共有的某些方法。
class Database extends Translation implements DriverInterface{ ...}
现在,只需要构建所需的方法。为此,我们将大量依赖 Eloquent。我在下面记录了一些比较有趣的方法,但是如果您有兴趣查看完整的实现,可以在GitHub 上查看。
public function allLanguages(){ return Language::all()->mapWithKeys(function ($language) { return [$language->language => $language->name ?: $language->language]; });} // $this->allLanguages(); // [// ‘en’ => ‘en’,// ‘fr’ => ‘fr’,// ‘es’ => ‘es’,// ];
在这里,我们使用 Eloquent 从数据库中获取所有语言,并遍历每个语言,以与文件驱动程序相同的格式返回语言。
public function getGroupTranslationsFor($language){ $translations = $this->getLanguage($language) ->translations() ->whereNotNull('group') ->where('group', 'not like', '%single') ->get() ->groupBy('group'); return $translations->map(function ($translations) { return $translations->mapWithKeys(function ($translation) { return [$translation->key => $translation->value]; }); });} // $this->getGroupTranslationsFor('en'); // [// ‘auth’ => [// ‘failed’ => ‘These credentials do not match our records’,// ],// ]
在这里,我们返回给定语言的所有组翻译。我们通过使用 Eloquent 获取该语言的所有翻译,其中 group 字段不包含 single
来做到这一点。然后,我们遍历 Eloquent 集合,以正确的格式返回结果。
public function addGroupTranslation($language, $key, $value = ''){ list($group, $key) = explode('.', $key); Language::where('language', $language) ->first() ->translations() ->updateOrCreate([ 'group' => $group, 'key' => $key, ], [ 'group' => $group, 'key' => $key, 'value' => $value, ]);}
在这里,我们按句点拆分键以获取组和翻译的键。然后,我们使用 Eloquent 的 updateOrCreate
方法来确定翻译是否存在。如果存在,我们更新它以防值已更改。如果不存在,我们为该语言创建一个新的翻译。
使用驱动程序
为了告诉 Laravel 实例化哪个驱动程序,我们将创建一个名为 TranslationManager
的新类,该类使用包配置来确定要返回哪个驱动程序。
public function resolve(){ $driver = $this->config['driver']; $driverResolver = studly_case($driver); $method = "resolve{$driverResolver}Driver"; return $this->{$method}();} protected function resolveFileDriver(){ return new File(...);} protected function resolveDatabaseDriver(){ return new Database(...);}
在这里,我们获取配置文件中定义的驱动程序值,将其转换为负责理解如何实例化驱动程序并返回它的方法。这样一来,将来添加新的驱动程序将成为一项更轻松的任务。例如,如果我们要在配置中添加一个名为 cloud
的新驱动程序,我们将向 TranslationManager
添加一个名为 resolveCloudDriver
的方法,该方法返回该驱动程序的新实例。
现在,在我们的服务提供程序的 register
方法中,我们可以将翻译驱动程序作为单例绑定到容器中。这意味着任何时候我们从容器中获取它,都会返回相同的实例。
use JoeDixon\Translation\Drivers\Translation; ... $this->app->singleton(Translation::class, function ($app) { return (new TranslationManager($app, $app['config']['translation'], $app->make(Scanner::class)))->resolve();});
您可能会注意到我们实际绑定的类是抽象 Translation
类。这是可能的,因为我们所有的驱动程序都扩展了此类。它很有用,因为它意味着无论实例化哪个驱动程序,都可以通过相同的方式从容器中获取它。
有了这个驱动程序,我们的包几乎可以发布到世界上了。在本系列的下一篇文章中,我们将讨论文档以及帮助用户开始使用该包所需的知识。
与往常一样,如果您有任何问题,请随时通过Twitter 联系。