我正在玩 Doctrine ODM,试图在我的 Mongo 文档中创建一些 i18n-able 字段。这就是我想在 Mongo 中实现的目标:
{
"title": {
"en": "Car",
"eu": "Autoa"
}
}
我想要的文档的 PHP API 是这样的:
$doc->getTitle()->setDefaultLocale('en');
$doc->getTitle(); // "Car"
$doc->getTitle()->get('eu'); // "Autoa"
$doc->getTitle()->set('es', 'Coche');
$doc->getTitle()->setTranslations([
'fr' => 'Voiture',
'eu' => 'Kotxea',
]);
$doc->getTitle()->getTranslations(); // ["en" => "Car", ...]
我尝试了两种方法,它们都有自己的陷阱。我一个都不喜欢。
自定义注释
我创建了一个类,它将成为文档和 mongo 之间的中间人。此类将放置在字段中,在本例中为 $title。
class Translation
{
protected $default;
protected $translations;
public function __construct(array $translations = array()) { /* ... */ }
public function get($locale) { /* ... */ }
public function getTranslations() { /* ... */ }
public function set($locale, $value) { /* ... */ }
public function setDefaultLocale($default) { /* ... */ }
public function setTranslations(array $translations = array()) { /* ... */ }
}
然后,我创建了一个自定义的 FieldType,它将 Mongo 数组转换为 Translation 中间人对象,反之亦然(convertTo* 方法似乎被 Doctrine 忽略并且等于closureTo* 方法,所以我将省略它们):
class TranslationType extends \Doctrine\ODM\MongoDB\Types\Type
{
public function convertToDatabaseValue($value) { /* ... */ }
public function convertToPHPValue($value) { /* ... */ }
public function closureToMongo()
{
return '$return = $value->getTranslations();';
}
public function closureToPHP()
{
return '$return = new \App\TransBundle\MongoDB\Translation($value);';
}
}
然后,我有我的注释:
/** @Annotation */
class Translation extends \Doctrine\ODM\MongoDB\Mapping\Annotations\AbstractField
{
public $type = 'translation';
}
和文件:
use App\TransBundle\MongoDB\Annotations\Translation;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/** @MongoDB\Document */
class Translated
{
/** @MongoDB\Id */
protected $id;
/** @Translation */
protected $title;
public function getId() { /* ... */ }
public function getTitle() { /* ... */ }
}
好的部分:
- 使用方便:一
use
、属性声明、注解和getter。 - 从 Mongo => Doctrine 中读取 OK。
- 满足 API 要求。
不好的部分:
- 不保存到 DB,我想这是因为 Translation 对象没有弄脏
title
父对象上的属性Translated
。 - $title 不会在对象创建时初始化为中间人 Translation 对象。
- 这将通过在构造函数中初始化对象来解决,但如果可能的话,我想尽量避免这种情况,以保持使用尽可能精简。我得想办法解决。
嵌入一
第二种方法包括使用嵌入式文档,这非常有效,但有它自己的小问题。:-)
首先,我的嵌入文档的基本翻译类,这个类将直接作用于类属性而不是数组属性:
class BaseTranslation
{
public function __construct(array $translations = array()) { /* ... */ }
public function get($locale) { /* ... */ }
public function getTranslations() { /* ... */ }
public function set($locale, $value) { /* ... */ }
public function setDefaultLocale($default) { /* ... */ }
public function setTranslations(array $translations = array()) { /* ... */ }
}
然后,要在我的项目中使用的 Translation 类,这将是实际的嵌入文档:
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/** @MongoDB\EmbeddedDocument */
class Translation extends BaseTranslation
{
/** @MongoDB\String */
protected $en;
/** @MongoDB\String */
protected $es;
/** @MongoDB\String */
protected $fr;
}
最后是文档
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/** @MongoDB\Document */
class Translated
{
/** @MongoDB\Id */
protected $id;
/** @MongoDB\EmbedOne(targetDocument="Translation") */
protected $title;
public function getId() { /* ... */ }
public function getTitle() { /* ... */ }
}
好的部分:
- 它可以很好地工作,读取和写入。
- 易于设置。
- 可以与任何数据类型一起使用,因此添加
isTranslated
i18n 布尔字段很容易,只需添加一个新的 TranslationBoolean 类。
不好的部分:
- 不是一个大问题,但语言环境在 Translation 类中是硬编码的,能够直接在数组上工作会很好,但这会在模式中添加另一个级别,并且类型强制可能会丢失。
- 与另一种方法一样,该属性不会被初始化,但在构造函数中初始化它很容易(就像任何 One2Many 关系一样)。
结论
我更喜欢第二种方法,它现在的工作方式。你知道如何克服这两种方法的坏部分吗?
谢谢!