我有以下协会:
ProductsTable hasMany PhotosTable
。
此外,
ProductsTable
使用Muffin/SlugBehavior
构建了一个独特的蛞蝓。
每次获得产品时,我都需要自动将
Photos
链接到 Products
。
所以我在
contain()
中使用 ProductsTable::beforeFind()
,因为我希望它独立于任何 Controller
。
这是我的
ProductsTable
:
// ProductsTable
class ProductsTable extends Table
{
public function initialize(array $config): void
{
$this->hasMany('Photos');
$this->addBehavior('Muffin/Slug.Slug', [
'displayField' => 'title', // 'title' is the field I want to build a unique slug from.
]);
}
public function beforeFind(Event $event, Query $query, ArrayObject $options, $primary)
{
$query->contain('Photos');
return $query;
}
}
在大多数情况下它运行良好,但是当我尝试创建具有已存在标题的产品(即具有已存在的slug)时会出现问题。
我有以下
InvalidArgumentException
:
无法加载照片关联。确保选择“产品”中的外键。
异常在 Muffin/SlugBehavior::_uniqueSlug()
行 $this->_table->exists($conditions)
处抛出。
我不明白到底是什么问题......
有人可以告诉我如何解决吗?
看看
\Cake\ORM\Table::exists()
做了什么:
public function exists($conditions): bool
{
return (bool)count(
$this->find('all')
->select(['existing' => 1])
->where($conditions)
->limit(1)
->disableHydration()
->toArray()
);
}
如您所见,这是一个常规查找,因此您的
beforeFind()
会影响它,并在该查询上强制 contain()
。现在,由于查询仅选择一个字段,这将导致您看到的错误,因为加载关联数据发生在单独的查询中,因此需要选择外键值(源表的主键) ,否则无法区分哪些记录属于彼此。
这可以通过许多不同的方式来解决。例如,您可以检查外键字段是否存在,然后才包含关联:
public function beforeFind(Event $event, Query $query, ArrayObject $options, $primary)
{
$select = $query->clause('select');
$autoFields = $select === [] || $query->isAutoFieldsEnabled();
$foreignKey = (array)$this->Photos->getForeignKey();
$aliasedForeignKey = (array)$this->Photos->getForeignKey();
$hasForeignKey = array_intersect($foreignKey, $select) === $foreignKey;
if (
$autoFields ||
$hasForeignKey
) {
return $query->contain('Photos');
}
return $query;
}
当然,这仍然可能会导致问题,例如,当您不小心不包含外键并且实际上想知道时,它可以抑制错误。
您还可以尝试仅排除
exists()
相关查询,例如通过检查确切的字段:
public function beforeFind(Event $event, Query $query, ArrayObject $options, $primary)
{
$select = $query->clause('select')
$existingFields = ['existing' => 1];
if ($select !== $existingFields) {
return $query->contain('Photos');
}
return $query;
}
另一种选择是明确说明您正在获取哪些数据。意思是创建表方法或 finders 返回您期望的数据,并相应地使用它们。少一点魔法,少一点惊喜。
public function findWithPhotos(Query $query, array $options)
{
return $query->contain('Photos');
}
$query = $productsTable->find('withPhotos');
我已使用
$query->enableAutoFields()
中的 beforeFind()
解决了该问题。