在 CakePHP 3.2 中更新之前如何检查项目是否属于 hasMany 关联

How to check if an item belongs to a hasMany association before updating in CakePHP 3.2

我想做什么:

我有估算,估算有项目 "EstimateItems"。更新 Estimate 时,更改的 EstimateItems 应该更新。 (使用 patchEntity)

这适用于我当前的代码,我唯一的问题是其他用户在编辑表单中更改 EstimateItem 的主键时可以编辑其他用户的 Estimate Items,因为在修补现有 EstimateItems 时,CakePHP 只看起来在 EstimateItem 的主键上并且不考虑关联。当 $protected estimate_id 设置为 false 时,仍然可以编辑 EstimateItem 的 estimate_id。

所以我需要的是 CakePHP 在更新之前或尝试更新时验证此 EstimateItem 是否属于当前关联。

我希望有人能告诉我我做错了什么或遗漏了什么。

Current Query

UPDATE 
  estimate_items 
SET 
  data = 'Test Query 1', 
  amount = 123456789, 
  tax_id = 3 
WHERE 
  id = 3

Expected Query

UPDATE 
  estimate_items 
SET 
  data = 'Test Query 1', 
  amount = 123456789, 
  tax_id = 3 
WHERE 
  id = 3 AND estimate_id = 1

当前代码:

Estimates -> Edit.ctp

<?php $this->Form->templates($formTemplates['default']); ?>
<?= $this->Form->create($estimate, ['enctype' => 'multipart/form-data']) ?>
    <fieldset>
        <legend><?= __('Offerte') ?></legend>

        <?= $this->Form->input('reference', ['label' => __('#Referentie'), 'autocomplete' => 'off']) ?>
        <?= $this->Form->input('client_id',
            [
                'type' => 'select',
                'empty' => true,
                'label' => __('Klant'),
                'options' => $clients
            ]
        )
        ?>

        <?php

        foreach($estimate->estimate_items as $key => $item){
        ?>
        <div class="item">
            <legend>Item</legend>
            <?= $this->Form->hidden('estimate_items.'. $key .'.id') ?>
            <?= $this->Form->input('estimate_items.'. $key .'.data', ['type' => 'text', 'label' => __('Beschrijving')]) ?>
            <?= $this->Form->input('estimate_items.'. $key .'.amount', ['type' => 'text', 'label' => __('Bedrag'), 'class' => 'input-date']) ?>
            <?= $this->Form->input('estimate_items.'. $key .'.tax_id',
                [
                    'type' => 'select',
                    'empty' => true,
                    'label' => __('Belasting type'),
                    'options' => $taxes
                ]
            )
            ?>
        </div>
        <?php
        }

        ?>

        <legend>Informatie</legend>
        <?= $this->Form->input('date', ['type' => 'text', 'label' => __('Offerte datum'), 'autocomplete' => 'off']) ?>
        <?= $this->Form->input('expiration', ['type' => 'text', 'label' => __('Verloop datum'), 'autocomplete' => 'off']) ?>
   </fieldset>
<?= $this->Form->button(__('Save')); ?>
<?= $this->Form->end() ?>

Estimates Controller

namespace App\Controller;

use App\Controller\AppController;
use Cake\Event\Event;
use Cake\ORM\TableRegistry;

class EstimatesController extends AppController
{
public function edit($id){
        $associated = ['EstimateItems'];

        $estimate = $this->Estimates->get($id, ['contain' => $associated]);

        $this->log($estimate);
        if($this->request->is(['patch', 'post', 'put'])) {

            $estimate = $this->Estimates->patchEntity($estimate, $this->request->data, [
                'associated' => $associated
            ]);

            $estimate->total = '0';
            $this->log($estimate);
            $this->log($this->request->data);

            if($this->Estimates->save($estimate, ['associated' => $associated])){
                $this->Flash->success(__('De offerte is bijgewerkt'));
                return $this->redirect(['action' => 'index']);
            }
        }

        $this->set('taxes', $this->Estimates->Taxes->find('list', [ 'keyField' => 'id', 'valueField' => 'tax_name' ]));
        $this->set('clients', $this->Estimates->Clients->find('list', [ 'keyField' => 'id', 'valueField' => 'companyname' ]));
        $this->set('estimate', $estimate);
    }
}

EstimatesTable

<?php
namespace App\Model\Table;

use Cake\ORM\Query;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\RulesChecker;
use Cake\ORM\Rule\IsUnique;

class EstimatesTable extends Table
{
public function initialize(array $config)
    {
        $this->addAssociations([
            'hasOne' => ['Taxes'],
            'belongsTo' => ['Companies', 'Clients'],
            'hasMany' => ['EstimateItems' => [
                'foreignKey' => 'estimate_id'
            ]]
        ]);

    }

public function buildRules(RulesChecker $rules){

        // A Node however should in addition also always reference a Site.
       $rules->add($rules->existsIn(['estimate_id'], 'EstimateItems'));

        return $rules;
    }

}

EstimateItem Entity

<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class EstimateItem extends Entity
{
    protected $_accessible = [
        '*' => false,
        'data' => true,
        'amount' => true,
        'tax_id' => true,
        'unit_id' => true
    ];
}

EstimateItemsTable

<?php
namespace App\Model\Table;

use Cake\ORM\Entity;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\RulesChecker;
use Cake\ORM\Rule\IsUnique;
use Cake\ORM\Query;


class EstimateItemsTable extends Table
{

    public function initialize(array $config)
    {
      $this->addAssociations([
            'belongsTo' => ['Estimates' => ['foreignKey' => 'estimate_id']],
            'hasOne' => ['Taxes' => ['foreignKey' => 'tax_id']]
        ]);
    }

Estimate Entity

<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class Estimate extends Entity
{

    /**
     * Fields that can be mass assigned using newEntity() or patchEntity().
     *
     * Note that when '*' is set to true, this allows all unspecified fields to
     * be mass assigned. For security purposes, it is advised to set '*' to false
     * (or remove it), and explicitly make individual fields accessible as needed.
     *
     * @var array
     */
    protected $_accessible = [
        '*' => false,
        'id' => false,
    ];
}

如果您不相信设置字段隐藏/不可编辑是不够的,那么您似乎需要通过 beforeSave 回调函数来实施检查。

在回调中,您可以在使用错误编辑的值覆盖它们之前检查该关系之前是否已经存在。

Markstory 在 github 上回复了我,解决方案归功于他: https://github.com/cakephp/cakephp/issues/9527

在Model/Table/EstimateItemsTable.php


<?php
namespace App\Model\Table;

use Cake\ORM\RulesChecker;
....
class EstimateItemsTable extends Table
{
....
public function buildRules(RulesChecker $rules){
        $rules->addUpdate(function($entity) {

          if (!$entity->dirty('estimate_id')) {
            return true;
          }
          return $entity->estimate_id == $entity->getOriginal('estimate_id');
        }, 'ownership', ['errorField' => 'estimate_id']);

        return $rules;
    }
}