【问题标题】:CakePHP 3: how to save an entity that has two levels of HABTM?CakePHP 3:如何保存具有两级 HABTM 的实体?
【发布时间】:2017-01-12 13:41:48
【问题描述】:

在这个问题中演变的表格是:

  • tarifas(属于 contratos)
  • 地区(habtm tarifas)
  • tarifas_areas (habtm turnos)
  • tarifas_areas_turnos

Tarifas也有Dias,但是这个关联没有问题,因为它是一个简单的关联。

关税表:

<?php
namespace App\Model\Table;

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

/**
 * Tarifas Model
 *
 * @property \Cake\ORM\Association\BelongsTo $Contratos
 * @property \Cake\ORM\Association\BelongsToMany $Areas
 * @property \Cake\ORM\Association\BelongsToMany $Dias
 *
 * @method \App\Model\Entity\Tarifa get($primaryKey, $options = [])
 * @method \App\Model\Entity\Tarifa newEntity($data = null, array $options = [])
 * @method \App\Model\Entity\Tarifa[] newEntities(array $data, array $options = [])
 * @method \App\Model\Entity\Tarifa|bool save(\Cake\Datasource\EntityInterface $entity, $options = [])
 * @method \App\Model\Entity\Tarifa patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
 * @method \App\Model\Entity\Tarifa[] patchEntities($entities, array $data, array $options = [])
 * @method \App\Model\Entity\Tarifa findOrCreate($search, callable $callback = null)
 */
class TarifasTable extends Table
{

    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->table('tarifas');
        $this->displayField('id');
        $this->primaryKey('id');

        $this->belongsTo('Contratos', [
            'foreignKey' => 'contrato_id',
            'joinType' => 'INNER'
        ]);
        $this->belongsToMany('Areas', [
            'foreignKey' => 'tarifa_id',
            'targetForeignKey' => 'area_id',
            'joinTable' => 'tarifas_areas'
        ]);
        $this->belongsToMany('Dias', [
            'foreignKey' => 'tarifa_id',
            'targetForeignKey' => 'dia_id',
            'joinTable' => 'tarifas_dias'
        ]);
    }

    /**
     * Default validation rules.
     *
     * @param \Cake\Validation\Validator $validator Validator instance.
     * @return \Cake\Validation\Validator
     */
    public function validationDefault(Validator $validator)
    {
        $validator
            ->integer('id')
            ->allowEmpty('id', 'create');

        $validator
            ->requirePresence('nome', 'create')
            ->notEmpty('nome');

        $validator
            ->date('dataInicial')
            ->requirePresence('dataInicial', 'create')
            ->notEmpty('dataInicial');

        $validator
            ->date('dataFinal')
            ->requirePresence('dataFinal', 'create')
            ->notEmpty('dataFinal');

        return $validator;
    }

    /**
     * Returns a rules checker object that will be used for validating
     * application integrity.
     *
     * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
     * @return \Cake\ORM\RulesChecker
     */
    public function buildRules(RulesChecker $rules)
    {
        $rules->add($rules->existsIn(['contrato_id'], 'Contratos'));

        return $rules;
    }

    public function salvaTarifasContrato($idContrato, $dadosTarifas){
        $tarifas = $this->newEntities(
            $dadosTarifas,
            ['associated' => ['TarifasAreas', 'TarifasAreas.TarifasAreasTurnos']]
        );
        debug($tarifas); die();
    }
}

区域表:

<?php
namespace App\Model\Table;

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

/**
 * Areas Model
 *
 * @property \Cake\ORM\Association\BelongsTo $Clientes
 * @property \Cake\ORM\Association\BelongsToMany $Bairros
 * @property \Cake\ORM\Association\BelongsToMany $Contratos
 * @property \Cake\ORM\Association\BelongsToMany $Tarifas
 *
 * @method \App\Model\Entity\Area get($primaryKey, $options = [])
 * @method \App\Model\Entity\Area newEntity($data = null, array $options = [])
 * @method \App\Model\Entity\Area[] newEntities(array $data, array $options = [])
 * @method \App\Model\Entity\Area|bool save(\Cake\Datasource\EntityInterface $entity, $options = [])
 * @method \App\Model\Entity\Area patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
 * @method \App\Model\Entity\Area[] patchEntities($entities, array $data, array $options = [])
 * @method \App\Model\Entity\Area findOrCreate($search, callable $callback = null)
 */
class AreasTable extends Table
{

    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->table('areas');
        $this->displayField('id');
        $this->primaryKey('id');

        $this->belongsTo('Clientes', [
            'foreignKey' => 'cliente_id',
            'joinType' => 'INNER'
        ]);
        $this->belongsToMany('Bairros', [
            'foreignKey' => 'area_id',
            'targetForeignKey' => 'bairro_id',
            'joinTable' => 'areas_bairros'
        ]);
        $this->belongsToMany('Contratos', [
            'foreignKey' => 'area_id',
            'targetForeignKey' => 'contrato_id',
            'joinTable' => 'contratos_areas'
        ]);
        $this->belongsToMany('Tarifas', [
            'foreignKey' => 'area_id',
            'targetForeignKey' => 'tarifa_id',
            'joinTable' => 'tarifas_areas'
        ]);
    }

    /**
     * Default validation rules.
     *
     * @param \Cake\Validation\Validator $validator Validator instance.
     * @return \Cake\Validation\Validator
     */
    public function validationDefault(Validator $validator)
    {
        $validator
            ->integer('id')
            ->allowEmpty('id', 'create');

        $validator
            ->allowEmpty('descricaoarea');

        return $validator;
    }

    /**
     * Returns a rules checker object that will be used for validating
     * application integrity.
     *
     * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
     * @return \Cake\ORM\RulesChecker
     */
    public function buildRules(RulesChecker $rules)
    {
        $rules->add($rules->existsIn(['cliente_id'], 'Clientes'));

        return $rules;
    }
}

TarifasAreasTable:

<?php
namespace App\Model\Table;

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

/**
 * TarifasAreas Model
 *
 * @property \Cake\ORM\Association\BelongsTo $Tarifas
 * @property \Cake\ORM\Association\BelongsTo $Areas
 * @property \Cake\ORM\Association\BelongsToMany $Turnos
 *
 * @method \App\Model\Entity\TarifasArea get($primaryKey, $options = [])
 * @method \App\Model\Entity\TarifasArea newEntity($data = null, array $options = [])
 * @method \App\Model\Entity\TarifasArea[] newEntities(array $data, array $options = [])
 * @method \App\Model\Entity\TarifasArea|bool save(\Cake\Datasource\EntityInterface $entity, $options = [])
 * @method \App\Model\Entity\TarifasArea patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
 * @method \App\Model\Entity\TarifasArea[] patchEntities($entities, array $data, array $options = [])
 * @method \App\Model\Entity\TarifasArea findOrCreate($search, callable $callback = null)
 */
class TarifasAreasTable extends Table
{

    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->table('tarifas_areas');
        $this->displayField('id');
        $this->primaryKey('id');

        $this->belongsTo('Tarifas', [
            'foreignKey' => 'tarifa_id',
            'joinType' => 'INNER'
        ]);
        $this->belongsTo('Areas', [
            'foreignKey' => 'area_id',
            'joinType' => 'INNER'
        ]);
        $this->belongsToMany('Turnos', [
            'foreignKey' => 'tarifas_area_id',
            'targetForeignKey' => 'turno_id',
            'joinTable' => 'tarifas_areas_turnos'
        ]);
    }

    /**
     * Default validation rules.
     *
     * @param \Cake\Validation\Validator $validator Validator instance.
     * @return \Cake\Validation\Validator
     */
    public function validationDefault(Validator $validator)
    {
        $validator
            ->integer('id')
            ->allowEmpty('id', 'create');

        return $validator;
    }

    /**
     * Returns a rules checker object that will be used for validating
     * application integrity.
     *
     * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
     * @return \Cake\ORM\RulesChecker
     */
    public function buildRules(RulesChecker $rules)
    {
        $rules->add($rules->existsIn(['tarifa_id'], 'Tarifas'));
        $rules->add($rules->existsIn(['area_id'], 'Areas'));

        return $rules;
    }
}

TarifasAreasTurnosTable:

<?php
namespace App\Model\Table;

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

/**
 * TarifasAreasTurnos Model
 *
 * @property \Cake\ORM\Association\BelongsTo $TarifaAreas
 * @property \Cake\ORM\Association\BelongsTo $Turnos
 *
 * @method \App\Model\Entity\TarifasAreasTurno get($primaryKey, $options = [])
 * @method \App\Model\Entity\TarifasAreasTurno newEntity($data = null, array $options = [])
 * @method \App\Model\Entity\TarifasAreasTurno[] newEntities(array $data, array $options = [])
 * @method \App\Model\Entity\TarifasAreasTurno|bool save(\Cake\Datasource\EntityInterface $entity, $options = [])
 * @method \App\Model\Entity\TarifasAreasTurno patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
 * @method \App\Model\Entity\TarifasAreasTurno[] patchEntities($entities, array $data, array $options = [])
 * @method \App\Model\Entity\TarifasAreasTurno findOrCreate($search, callable $callback = null)
 */
class TarifasAreasTurnosTable extends Table
{

    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->table('tarifas_areas_turnos');
        $this->displayField('id');
        $this->primaryKey('id');

        $this->belongsTo('TarifaAreas', [
            'foreignKey' => 'tarifa_area_id',
            'joinType' => 'INNER'
        ]);
        $this->belongsTo('Turnos', [
            'foreignKey' => 'turno_id',
            'joinType' => 'INNER'
        ]);
    }

    /**
     * Default validation rules.
     *
     * @param \Cake\Validation\Validator $validator Validator instance.
     * @return \Cake\Validation\Validator
     */
    public function validationDefault(Validator $validator)
    {
        $validator
            ->integer('id')
            ->allowEmpty('id', 'create');

        $validator
            ->numeric('valor')
            ->requirePresence('valor', 'create')
            ->notEmpty('valor');

        return $validator;
    }

    /**
     * Returns a rules checker object that will be used for validating
     * application integrity.
     *
     * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
     * @return \Cake\ORM\RulesChecker
     */
    public function buildRules(RulesChecker $rules)
    {
        $rules->add($rules->existsIn(['tarifa_area_id'], 'TarifaAreas'));
        $rules->add($rules->existsIn(['turno_id'], 'Turnos'));

        return $rules;
    }
}

这是我要保存的数据(它是 json 的一部分):

"tarifas": [
        {
            "nome": "T1",
            "dataInicial": "2017-01-10",
            "dataFinal": "2018-01-10",
            "dias": [
                {
                    "id": 1,
                    "_joinData": [] 
                },
                {
                    "id": 2,
                    "_joinData": [] 
                },
                {
                    "id": 3,
                    "_joinData": [] 
                },
                {
                    "id": 4,
                    "_joinData": [] 
                },
                {
                    "id": 5,
                    "_joinData": [] 
                }
            ],
            "TarifasAreas": [
                {
                    "area_id": 3,
                    "TarifasAreasTurnos": [
                        {
                            "turno_id": 4,
                            "valor": 20
                        }
                    ]
                }
            ]
        }
    ]

所以,在TarifasTable 中,我编写了这个函数来保存所有tarifas(但我仍在调试):

public function salvaTarifasContrato($idContrato, $dadosTarifas){
    $tarifas = $this->newEntities(
        $dadosTarifas,
        ['associated' => ['TarifasAreas', 'TarifasAreas.TarifasAreasTurnos']]
    );
    debug($tarifas); die();
}

我得到了这个回报:

{
  "message": "Cannot marshal data for \"TarifasAreas\" association. It is not associated with \"Tarifas\".",
  "url": "/project/api/contratos",
  "code": 500
}

那么,我如何告诉 CakePHP 这些表是如何相互关联的呢?有没有办法一次性保存所有数据?

【问题讨论】:

    标签: cakephp cakephp-3.0


    【解决方案1】:

    您需要在TarifasTarifasAreas 之间定义hasMany 关系。在表上定义belongsToMany 关系时,它只关联明确声明的表,而不关联直通表。

    // TarifasTable.php
    $this->hasMany('TarifasAreas');
    

    https://book.cakephp.org/3.0/en/orm/associations.html#hasmany-associations

    只要TarifasAreasTarifas 的外键是tarifa_id,这应该可以工作!

    【讨论】:

    • 是的,它有效!但我不得不更改表名,因为似乎 CakePHP 3 有一个错误,即 ORM“无法描述 TarifasAreas 表:它有 0 列”。
    • 这很奇怪!它与命名约定有什么关系吗? - book.cakephp.org/3.0/en/intro/… - 通常连接表名是按字母顺序排列的 AreasTarifas
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-14
    • 2013-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多