如何在 PHP-Laravel 的一个模型中拥有多个相同的多态关系?

问题描述

我正在处理一个挑战,我想创建一个多态模型本地化。这将包括长文本列,例如 defaulten_usde_de、...

现在想象另一个模型产品。产品通常具有文本属性,如名称、描述、...这里是我可以使用 Localizations 的多态关系的部分。 localizations 表应该具有这种类型的结构:

id localizable_id localizable_type en_us de_de
1 25 App\Model\Product 电话 null 电话
2 25 App\Model\Product 市面上最好的没有音频插孔的手机 null Das beste Telefon auf dem Markt ohne Audioanschluss
3 15 App\Model\Article Top 10 产品 null Top 10 产品
4 15 App\Model\Job 销售员 null Verkäufer

如果我希望 产品 具有可本地化的名称和描述,那么根据 Laravel 文档,您应该在我的代码中看到类似这样的内容

class Product extends Model
{
  public function name() // Expecting model with default attribute 'Phone'
  {
    return $this->morphOne(Localization::class,'localizable');
  }

  public function description() // Expecting model with default attribute 'The best phone on the market without an audio jack'
  {
    return $this->morphOne(Localization::class,'localizable');
  }
}

无论多么明显,它都不会正常工作,因为我不能指望两个相同的方法返回不同的值。

另一方面,如果我想遵循 Laravel 的约定,Localization 模型应该如下所示:

class Localizable extends Model
{
  public function localizable()
  {
    return $this->morphTo(__FUNCTION__,'localizable_type','localizable_id');
  }
}

正如你在上面的例子中看到的,多态关系与我需要的相反。问题是我想要一个包含所有字符串的表格,这些字符串可能会被翻译(localizables)并在许多其他模型中使用它们,而不仅仅是 Product,比如 购物工作文章、...

我需要以某种方式实现 localizable_types 之间的区别,还需要实现它应该相关的列/属性。实现这一目标的最佳方法是什么?

解决方法

我终于找到了满足我期望的解决方法。尽管这不是最漂亮(或 Laravel)的方式。我从在 GitHub here 上发现的类似斗争中得到启发。

此解决方案的重点是覆盖用于确定 getMorphClass() 列值的 Product*_type 方法。我的产品模型:

class Product extends Model
{
  protected $morphClass = null; // Create an attribute that will not be saved into the DB
  public function getMorphClass() // Method for determinating the type value
  {
    return $this->morphClass? : self::class;
  }

  public function name()
  {
    $this->morphClass = self::class . '.name'; // App\Models\Product.name
    return $this->morphOne(Localization::class,'localizable');
  }

  public function description()
  {
    $this->morphClass = self::class . '.description'; // App\Models\Product.description
    return $this->morphOne(Localization::class,'description');
  }

}

上述修改将影响 Laravel 将如何将 Localization 保存到数据库中。现在,需要从关系的另一侧完成一些工作 - custom polymorphic types。更新 AppServiceProvider 文件中的 app/Providers/AppServiceProvider.php 类。您必须将 morphMap 添加到 boot() 方法:

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
  // ...any of your previous modifications...

  Relation::morphMap([
    Product::class . '.name' => Product::class,Product::class . '.description' => Product::class,]);
}

但请注意:一旦您决定在模型中使用多个相同类型的变形,您必须在调用 {{1} 之前更改每个动态方法中的受保护属性 $morphClass } 或 morphOne()。否则,您可能会将描述保存到名称中,反之亦然。

此外,当您在另一端使用 morphMany() 方法时,您只会获得 Product - 如果您需要知道 Product 模型正在相关,我建议您创建一些提取上下文的方法。

请随时评论此方法中的任何潜在威胁。