contain() 有时会导致 Cakephp4 出现问题

问题描述 投票:0回答:2

我有以下协会:

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)
处抛出。

我不明白到底是什么问题......

有人可以告诉我如何解决吗?

php slug cakephp-4.x
2个回答
1
投票

看看

\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');

0
投票

我已使用

$query->enableAutoFields()
中的
beforeFind()
解决了该问题。

© www.soinside.com 2019 - 2024. All rights reserved.