【问题标题】:Laravel 6 API RESTful with many to many relationship filteredLaravel 6 API RESTful,过滤了多对多关系
【发布时间】:2020-05-31 20:26:11
【问题描述】:

我正在使用 laravel 6 开发多语言 API,我的数据库中有这种情况:

Categories
id
other not relevants fields

Languages:
id
name
code

Category_Language
id
language_id
category_id
name --> this is the name of the category in the specific language.

现在我有 2 个模型,第一个用于类别

class Category extends Model
{    
    public function languages()
    {
        return $this->belongsToMany('App\Models\v1\Language')->withTimestamps()->withPivot('name');
    }
}

和第二个模型

class Language extends Model
{
    protected $fillable = ['code', 'name', 'image_id', 'enabled'];

    public function categories() {
        return $this->belongsToMany('App\Models\v1\Category')->withTimestamps()->withPivot('name');
    }
}

在我的逻辑中(我正在使用服务模式),我在创建类别时使用了这种方法,每次传递这样的 JSON 对象时:

{
  "names": [
    {
      "languageId": 1,
      "name": "Hello"
    },
    {
      "languageId": 2,
      "name": "Hola"
    }
  ]
}

首先我创建一个类别(验证语言的 id 是否真的存储在数据库中),然后使用多对多的 laravel 功能,我将语言和名称附加到类别模型中,如下所示:

 foreach($request->names as $name) {
    $category->languages()->attach($name['languageId'], ['name' => $name['name']]);
 }

现在这似乎工作得很好,不用像这样使用 API 资源过滤语言就可以检索所有类别:

public function toArray($request)
{
    $category = [];
    $category['id'] = $this->id;
    $category['languages'] = [];

    $category['languages'] = $this->languages->map(function ($language) {
        return [
            'languageId' => $language->id,
            'languageCode' => $language->code,
            'languageName' => $language->name,
            'categoryName' => $language->pivot->name,
        ];
    });

    return $category;
}

这是具有 3 种语言的 3 个类别的输出(伪造数据):

array:3 [
  "data" => array:3 [
    0 => array:4 [
      "id" => 1
      "languages" => array:3 [
        0 => array:4 [
          "languageId" => 1
          "languageCode" => "pa"
          "languageName" => "Kacey Trantow"
          "categoryName" => "quasi"
        ]
        1 => array:4 [
          "languageId" => 2
          "languageCode" => "ne"
          "languageName" => "Mr. Alexandre Heathcote"
          "categoryName" => "perferendis"
        ]
        2 => array:4 [
          "languageId" => 3
          "languageCode" => "kj"
          "languageName" => "Mr. Misael Robel"
          "categoryName" => "repudiandae"
        ]
      ]
      "imageUrl" => null
      "enabled" => true
    ]
    1 => array:4 [
      "id" => 2
      "languages" => array:3 [
        0 => array:4 [
          "languageId" => 1
          "languageCode" => "pa"
          "languageName" => "Kacey Trantow"
          "categoryName" => "non"
        ]
        1 => array:4 [
          "languageId" => 2
          "languageCode" => "ne"
          "languageName" => "Mr. Alexandre Heathcote"
          "categoryName" => "vitae"
        ]
        2 => array:4 [
          "languageId" => 3
          "languageCode" => "kj"
          "languageName" => "Mr. Misael Robel"
          "categoryName" => "suscipit"
        ]
      ]
      "imageUrl" => null
      "enabled" => true
    ]
    2 => array:4 [
      "id" => 3
      "languages" => array:3 [
        0 => array:4 [
          "languageId" => 1
          "languageCode" => "pa"
          "languageName" => "Kacey Trantow"
          "categoryName" => "molestiae"
        ]
        1 => array:4 [
          "languageId" => 2
          "languageCode" => "ne"
          "languageName" => "Mr. Alexandre Heathcote"
          "categoryName" => "esse"
        ]
        2 => array:4 [
          "languageId" => 3
          "languageCode" => "kj"
          "languageName" => "Mr. Misael Robel"
          "categoryName" => "beatae"
        ]
      ]
      "imageUrl" => null
      "enabled" => true
    ]
  ]

现在,问题是当我想过滤仅传递 1 种特定语言的类别时,这是一个经典用例,当用户在导航期间仅使用 1 种语言时,如果我只想要 1 种语言(以及 1 个名称在类别)我需要什么样的操作?

我已经用特定的过滤器组织了服务,并为任何集合排序,但这种需求似乎让我抓狂!

所以这是我构建过滤器、排序和动态包含的最终 getCategories 方法:

-

想像得到这样的查询字符串:

http://localhost:8000/api/v1/categories?orderBy=id:asc&include=language&language.code=EN

我想要这样的回应:

array:3 [
      "data" => array:3 [
        0 => array:4 [
          "id" => 1
          "languages" => array:3 [
            0 => array:4 [
              "languageId" => 1
              "languageCode" => "EN"
              "languageName" => "English"
              "categoryName" => "quasi"
            ]
          ]
        ]
        1 => array:4 [
          "id" => 2
          "languages" => array:3 [
            0 => array:4 [
              "languageId" => 1
              "languageCode" => "EN"
              "languageName" => "English"
              "categoryName" => "non"
            ]
          ]
        ]
        2 => array:4 [
          "id" => 3
          "languages" => array:3 [
            0 => array:4 [
              "languageId" => 1
              "languageCode" => "EN"
              "languageName" => "English"
              "categoryName" => "molestiae"
            ]
          ]
        ]
      ]

我需要这样的东西,但一般来说,因为我们有其他实体使用相同的多语言系统逻辑。

为了澄清我想要的是像这样的连接查询:

SELECT *
FROM categories ca INNER JOIN category_language cl ON ca.id =cl.category_id
                 INNER JOIN languages lan ON lan.id = cl.language_id
WHERE lan.code = 'EN'; 

您将仅检索英语语言的类别列表...

感谢您的任何建议和帮助!

【问题讨论】:

    标签: php laravel many-to-many laravel-6 laravel-resource


    【解决方案1】:

    我认为这并不是最优雅的方法,但我已经通过一些解决方法解决了。

    首先,我不在查询字符串中传递语言代码,而是在使用 X 本地化参数的 Header 参数中传递,就像在这个 tutorial for laravel with multilanguages 中解释的那样。

    其次,当检索所有资源时,我总是在服务方法中返回 query->paginate():

    public function getCategories(Request $request)
    {
        $sort = $this->buildSort($request->sort ?? '', 'id', 'asc');
        $where = $this->buildWhere($request->where ?? '');
        $includes = $this->buildWith($request->include ?? '');
        $query = Category::orderBy($sort[0], $sort[1]);
    
        if (!empty($include)) {
            $query = $query->with($includes);
        }
    
        if (!empty($where)) {
            $query = $query->where($where);
        }
    
        return $query->paginate();
    
    }
    

    最后,我将请求标头截获到 Category API 资源中,如果设置了 X-localization 参数,我会在地图中过滤语言数组,如下所示:

    public function toArray($request)
    {
        $languageCode = $request->header('X-localization') ?? null;
    
        $category = [];
        $category['id'] = $this->id;
        $category['languages'] = [];
    
        $languages = $this->languages;
    
        if ($languageCode) {
            $languages = $languages->where('code', '=', $languageCode);
        }
    
        $languages = $languages->map(function ($language) use ($languageCode, $category) {
            return [
                'languageId' => $language->id,
                'languageCode' => $language->code,
                'languageName' => $language->name,
                'categoryName' => $language->pivot->name,
            ];
        });
    
        $category['languages'] = $languages;
    
    
        return $category;
    
    }
    

    结果正是我想要的,没有在我的项目中使用纯 SQL(假数据):

    array:3 [
      "data" => array:3 [
        0 => array:4 [
          "id" => 1
          "languages" => array:1 [
            0 => array:4 [
              "languageId" => 1
              "languageCode" => "ho"
              "languageName" => "Dott. Orfeo Sartori"
              "categoryName" => "rem"
            ]
          ]
          "imageUrl" => null
          "enabled" => true
        ]
        1 => array:4 [
          "id" => 2
          "languages" => array:1 [
            0 => array:4 [
              "languageId" => 1
              "languageCode" => "ho"
              "languageName" => "Dott. Orfeo Sartori"
              "categoryName" => "accusamus"
            ]
          ]
          "imageUrl" => null
          "enabled" => true
        ]
        2 => array:4 [
          "id" => 3
          "languages" => array:1 [
            0 => array:4 [
              "languageId" => 1
              "languageCode" => "ho"
              "languageName" => "Dott. Orfeo Sartori"
              "categoryName" => "totam"
            ]
          ]
          "imageUrl" => null
          "enabled" => true
        ]
      ]
      "links" => array:4 [
        "first" => "http://localhost/api/v1/categories?page=1"
        "last" => "http://localhost/api/v1/categories?page=1"
        "prev" => null
        "next" => null
      ]
      "meta" => array:7 [
        "current_page" => 1
        "from" => 1
        "last_page" => 1
        "path" => "http://localhost/api/v1/categories"
        "per_page" => 15
        "to" => 3
        "total" => 3
      ]
    ]
    

    进程以退出代码 1 结束

    【讨论】:

      【解决方案2】:

      你可以像这样加入雄辩:

      $language_code=$request->input('language_code');

          $values=Category::query()->join('category_language','category_language.category_id','=','categories.id')
      ->join('languages','languages.id','=','category_language.language_id')
      ->where('languages.code','=',$language_code)
      ->select('categories.*','languages.code')
          ->orderBy('categories.id')->get();
      

      【讨论】:

      • 有点不同,其中不作为类别数组中语言的过滤器。请参阅我对帖子的最后编辑以获得更好的解释
      猜你喜欢
      • 2020-05-22
      • 1970-01-01
      • 2018-05-20
      • 2020-06-13
      • 1970-01-01
      • 2018-04-19
      • 2013-08-08
      • 2013-09-14
      • 2021-08-12
      相关资源
      最近更新 更多