CakePHP 3:如何保存具有两级HABTM的实体?

时间:2017-01-12 13:41:49

标签: cakephp cakephp-3.0

这个问题的演变表是:

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

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

TarifasTable:

<?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();
    }
}

AreasTable:

<?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这些表如何相互关联?有没有办法一次性保存这些数据?

1 个答案:

答案 0 :(得分:1)

您需要在hasManyTarifas之间定义TarifasAreas关系。在表上定义belongsToMany关系时,它只涉及明确声明的表而不是直通表。

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

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

只要TarifasAreas Tarifas中的外键为tarifa_id,这就行了!