CakePHP 3.x:使用文本输入

时间:2017-06-22 18:43:34

标签: php cakephp cakephp-3.0

这个问题对我来说似乎很基础,所以我想知道它出了什么问题。

我将通过一个简单的例子展示我的问题。我使用简单的 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']);

1 个答案:

答案 0 :(得分:0)

使用beforeMarshal相应地修改数据是可行的方法,但是您需要在书籍数据中填充外键,即设置philosopher_id,然后删除{{ 1}}:

philosopher

unset($data['philosopher']); $data['philosopher_id'] = $result->id; 仅在更新现有记录时使用。

即使你没有预料到,philosopher.id也可能会纾困,因为可能没有设置实例

最后但并非最不重要的是,TableRegistry::exists()是危险的,它可能是SQL injection漏洞,因为传递的数组的关键手边将按原样插入到查询中(整个value也可以是一个字符串,也可以按原样插入,并且可以由用户定义。如果您想使用这种可配置/可重用/动态功能,那么您应该为字段名实现白名单,并自己构建条件数组。