Saving associated not working, only saving the main entity data with no erros

Hello, im trying to figure out why the save() with associated option not working, when i create the main entity and the associated one from a form and save it works, but when i try loading it from database changing some data and saving only the main entity data is saved.

  • cake version 3.6.3;
  • disabled validate and checkRules for both;
  • debug shows the correct modified data with fields marked as dirty for both;
  • save() return true but only the main entity data is saved (associated data still marked as dirty after save);
    i don’t know where to look the problem as there are no erros

save function:

public function baixar($id = null)
{
    if (!$id) {
        throw new NotFoundException(__('Invalid ID'));
    }
    $recibo = $this->Recibos->get($id, ['contain' => ['Pagamentos']]);
    if ($this->request->is(['post', 'put'])) {
        $recibo->recibo_situacao_id = 2;
        $recibo->data_baixa = Chronos::now();
        foreach ($recibo->pagamentos as $pagamento) {
            $pagamento->pagamento_situacao_id = 2;
        }
        debug($recibo); //generates correct data with the dirty fields of the main entity and the associated one
        if ($this->Recibos->save($recibo, [
                'associated' => [
                    'Pagamentos' => ['validate' => false, 'checkRules' => false]
                ],
                'validate' => false, 
                'checkRules' => false
            ])) {
            debug($recibo);
        } else {
            debug('error');
        }
    }
} 

entity debug before and after save:

//debug before save()
/src/Controller/Admin/RecibosController.php (line 185)
object(App\Model\Entity\Recibo) {
	'id' => (int) 37,
	'pagamento_tipo_id' => (int) 1,
	'recibo_situacao_id' => (int) 2,
	'data_baixa' => object(Cake\Chronos\Chronos) {
		'time' => '2018-05-16 13:46:18.978997',
		'timezone' => 'America/Sao_Paulo',
		'hasFixedNow' => false
	},
	'pagamentos' => [
		(int) 0 => object(App\Model\Entity\Pagamento) {
			'id' => (int) 68,
			'pagamento_situacao_id' => (int) 2,
			'recibo_id' => (int) 37,
			'valor' => (float) 72,
			'[new]' => false,
			'[accessible]' => [
				'*' => true
			],
			'[dirty]' => [
				'pagamento_situacao_id' => true
			],
			'[original]' => [
				'pagamento_situacao_id' => (int) 1
			],
			'[virtual]' => [],
			'[errors]' => [],
			'[invalid]' => [],
			'[repository]' => 'Pagamentos'
		},
		(int) 1 => object(App\Model\Entity\Pagamento) {
			'id' => (int) 69,
			'pagamento_situacao_id' => (int) 2,
			'recibo_id' => (int) 37,
			'valor' => (float) 342,
			'[new]' => false,
			'[accessible]' => [
				'*' => true
			],
			'[dirty]' => [
				'pagamento_situacao_id' => true
			],
			'[original]' => [
				'pagamento_situacao_id' => (int) 1
			],
			'[virtual]' => [],
			'[errors]' => [],
			'[invalid]' => [],
			'[repository]' => 'Pagamentos'
		
		},
		(int) 2 => object(App\Model\Entity\Pagamento) {
			'id' => (int) 70,
			'pagamento_situacao_id' => (int) 2,
			'recibo_id' => (int) 37,
			'valor' => (float) 100,
			'[new]' => false,
			'[accessible]' => [
				'*' => true
			],
			'[dirty]' => [
				'pagamento_situacao_id' => true
			],
			'[original]' => [
				'pagamento_situacao_id' => (int) 1
			],
			'[virtual]' => [],
			'[errors]' => [],
			'[invalid]' => [],
			'[repository]' => 'Pagamentos'
		}
	],
	'[new]' => false,
	'[accessible]' => [
		'*' => true
	],
	'[dirty]' => [
		'recibo_situacao_id' => true,
		'data_baixa' => true
	],
	'[original]' => [
		'data_baixa' => object(Cake\I18n\FrozenTime) {
			'time' => '2018-05-16T13:43:57-03:00',
			'timezone' => 'America/Sao_Paulo',
			'fixedNowTime' => false
		}
	],
	'[virtual]' => [],
	'[errors]' => [],
	'[invalid]' => [],
	'[repository]' => 'Recibos'
}


//debug after save(), the only diference is the main entity dirty array is empty because of the save()
/src/Controller/Admin/RecibosController.php (line 191)

object(App\Model\Entity\Recibo) {
	'id' => (int) 37,
	'pagamento_tipo_id' => (int) 1,
	'recibo_situacao_id' => (int) 2,
	'data_baixa' => object(Cake\Chronos\Chronos) {
		'time' => '2018-05-16 13:46:18.978997',
		'timezone' => 'America/Sao_Paulo',
		'hasFixedNow' => false
	},
	'pagamentos' => [
		(int) 0 => object(App\Model\Entity\Pagamento) {
			'id' => (int) 68,
			'pagamento_situacao_id' => (int) 2,
			'recibo_id' => (int) 37,
			'valor' => (float) 72,
			'[new]' => false,
			'[accessible]' => [
				'*' => true
			],
			'[dirty]' => [
				'pagamento_situacao_id' => true
			],
			'[original]' => [
				'pagamento_situacao_id' => (int) 1
			],
			'[virtual]' => [],
			'[errors]' => [],
			'[invalid]' => [],
			'[repository]' => 'Pagamentos'
		},
		(int) 1 => object(App\Model\Entity\Pagamento) {
			'id' => (int) 69,
			'pagamento_situacao_id' => (int) 2,
			'recibo_id' => (int) 37,
			'valor' => (float) 342,
			'[new]' => false,
			'[accessible]' => [
				'*' => true
			],
			'[dirty]' => [
				'pagamento_situacao_id' => true
			],
			'[original]' => [
				'pagamento_situacao_id' => (int) 1
			],
			'[virtual]' => [],
			'[errors]' => [],
			'[invalid]' => [],
			'[repository]' => 'Pagamentos'
		
		},
		(int) 2 => object(App\Model\Entity\Pagamento) {
			'id' => (int) 70,
			'pagamento_situacao_id' => (int) 2,
			'recibo_id' => (int) 37,
			'valor' => (float) 100,
			'[new]' => false,
			'[accessible]' => [
				'*' => true
			],
			'[dirty]' => [
				'pagamento_situacao_id' => true
			],
			'[original]' => [
				'pagamento_situacao_id' => (int) 1
			],
			'[virtual]' => [],
			'[errors]' => [],
			'[invalid]' => [],
			'[repository]' => 'Pagamentos'
		}
	],
	'[new]' => false,
	'[accessible]' => [
		'*' => true
	],
	'[dirty]' => [],
	'[original]' => [],
	'[virtual]' => [],
	'[errors]' => [],
	'[invalid]' => [],
	'[repository]' => 'Recibos'

}

Sorry, I didn’t check carefully your code.

But in general 1. check model table associations.
2. check foreign keys are provided when you are updating (editing). Eg - on the edit users form, you would have Users.name, etc. but also the associated Profiles.user_id (hidden), Profiles.blog_link, …

  • there are no forms, im loading from database changing the fields and saving;
  • i think there are no erros in the association as the save associated works in the add() function and as debug shown the foreign keys are correct;

its a simple association:

class RecibosTable extends Table
{
    public function initialize(array $config)
    {
        $this->hasMany('Pagamentos', [
            'className' => 'Pagamentos',
            'foreignKey' => 'recibo_id',
            'propertyName' => 'pagamentos',
            'dependent' => true,
            'cascadeCallbacks' => true
        ]);
    }
}

class PagamentosTable extends Table
{
    public function initialize(array $config)
    {
        $this->belongsTo('Recibos', [
            'className' => 'Recibos',
            'foreignKey' => 'recibo_id'
        ]);
    }
}

Oh then you need to supply the foreign key in the save data array, eg Profile.user_id

the save data is already a valid entity with keys associated:

object(App\Model\Entity\Recibo) {
	'id' => (int) 37, //<<< MAIN ENTITY ID
	'recibo_situacao_id' => (int) 2,
	'data_baixa' => object(Cake\Chronos\Chronos) {
		'time' => '2018-05-16 13:46:18.978997',
		'timezone' => 'America/Sao_Paulo',
		'hasFixedNow' => false
	},
	'pagamentos' => [
		(int) 0 => object(App\Model\Entity\Pagamento) {
			'id' => (int) 68,
			'pagamento_situacao_id' => (int) 2,
			'recibo_id' => (int) 37, //<< associated reference to main

ok i found the problem, when manually setting properties you need set the associated property name as dirty:

$main_entity->setDirty('associated_entity_property_name', true);

2 Likes

I just want to write this :slight_smile: going through this my self few years ago…
If I remember right, you can set whole ‘association’ as dirty and it can work.
It is worth to debug entity after patchEntity/newEntity and after save(), you can see there dirty flags.

Glad you solved it.

The issue here comes from

 foreach ($recibo->pagamentos as $pagamento) {
     $pagamento->pagamento_situacao_id = 2;
 }

The reason is that due to the way that PHP handles object references, the original Recibo entity never gets told that the pagamentos property has been modified (Because the property has not been directly modified)