这个问题对我来说似乎很基础,所以我想知道它出了什么问题。
我将通过一个简单的例子展示我的问题。我使用简单的 belongsTo 关系创建了两个表。
create table philosophers (
id int unsigned primary key auto_increment,
full_name varchar(255) not null unique
);
create table books (
id int unsigned primary key auto_increment,
title varchar(255) unique not null,
philosopher_id int unsigned not null,
foreign key `philosopher_id` (philosopher_id) references philosophers(id)
)
用新鲜的cakePHP 3.4.8安装烘烤所有东西。 到现在为止还挺好。这是一个问题:
我想在一个文本框中写下Philosopher的名字,并让CakePHP将它与现有名称相关联(如果它已存在),或者添加一个新名称(如果它尚不存在)。所以,根据惯例,我替换
echo $this->Form->control('philosopher_id', ['options' => $philosophers]);
在文件 src / Template / Books / add.ctp 中,附带:
echo $this->Form->control('philosopher.full_name');
在第二种情况下(添加一个新条目),它可以很好地工作,添加外键和所有。
为了实现第一个选项,我尝试了
'checkExisting'
阶段为关联表隐式设置$entity->save()
。id
实体中访问Philosopher
。beforeMarshal
事件中添加ID的行为。这是行为:
见下文
它似乎不想创建现有实体。 我知道我可以做here所说的内容,但这实际上完全绕过了验证。
我几乎可以肯定我错过了什么......希望我知道它是什么。
编辑:我更新并更正了行为,并考虑了@ndm的解决方案。
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
use Cake\Event\Event;
use ArrayObject;
use Cake\ORM\TableRegistry;
use Cake\Utility\Inflector;
/**
* This class prevents the belongsTo relation from
* always creating new entries, by modifying the data
* before it is marshalled.
*
* The config should have an entry called 'fields':
*
* - 'fields' An array of field names, formatted
* according to cakePHP conventions
* for BelongsTo associations.
*/
class MarshalAssocBehavior extends Behavior {
protected $_defaultConfig = [
'fields' => []
];
public function beforeMarshal (Event $event,
ArrayObject $data,
ArrayObject $options) {
$fields = $this->getConfig('fields');
foreach ($fields as $field) {
$temp = explode('.', $field);
$fd_name = $temp[0];
$column = $temp[1];
unset($temp);
/*
* If @$data does not contain required keys,
* skip and evaluate next config block.
*/
if ( !array_key_exists($fd_name, $data)
|| !array_key_exists($column, $data[$fd_name])
) continue;
$table_name = Inflector::pluralize(Inflector::camelize($fd_name));
$table = TableRegistry::get($table_name);
/**
* @var Cake\Datasource\EntityInterface $result
*/
$result = $table->find()
// value (user-provided) is escaped by Cake
->where([$column => $data[$fd_name][$column]])
->first();
if ($result) {
unset($data[$fd_name]);
$data[$fd_name.'_id'] = $result->id;
}
}
}
}
将其合并到我的BooksController
:
public function add() {
$this->Books
->addBehavior('MarshalAssoc', [
'fields' => ['philosopher.full_name']);
答案 0 :(得分:0)
使用beforeMarshal
相应地修改数据是可行的方法,但是您需要在书籍数据中填充外键,即设置philosopher_id
,然后删除{{ 1}}:
philosopher
unset($data['philosopher']);
$data['philosopher_id'] = $result->id;
仅在更新现有记录时使用。
即使你没有预料到,philosopher.id
也可能会纾困,因为可能没有设置实例 !
最后但并非最不重要的是,TableRegistry::exists()
是危险的,它可能是SQL injection漏洞,因为传递的数组的关键手边将按原样插入到查询中(整个value也可以是一个字符串,也可以按原样插入,并且可以由用户定义。如果您想使用这种可配置/可重用/动态功能,那么您应该为字段名实现白名单,并自己构建条件数组。