I'm currently working on finalizing an upgrade from CakePHP 3.3 to 4.4. I now have the same problem in a few places, which all are the same SQLSTATE error:
SQLSTATE[HY000]: General error: 1364 Field 'cert_request' doesn't have a default value
The code looks currently like this:
$cert = $this->Certs->newEmptyEntity();
$data = $this->request->getData();
if(array_key_exists('certificate', $data) && !is_null($data['certificate'])) {
$certificate = $data['certificate'];
$cert_string = $certificate;
$certificated = openssl_x509_parse($certificate);
} else {
if ($this->request->is('post')) {
$data['effective_date'] = date('Y-m-d H:i', strtotime($data['effective_date']));
$data['expiry_date'] = date('Y-m-d H:i', strtotime($data['expiry_date']));
$cert = $this->Certs->patchEntity($cert, $data);
// Database Error happens here during save()
if ($this->Certs->save($cert)) {
$this->Flash->success(__('The cert has been saved.'));
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error(__('The cert could not be saved. Please, try again.'));
}
}
}
Mind, that $data
does indeed not contain 'cert_request' and other fields related to this problem.
namespace App\Model\Table;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Certs Model
*
* @property \Cake\ORM\Association\BelongsTo $Requests
* @property \Cake\ORM\Association\BelongsTo $CertTemplates
* @property \Cake\ORM\Association\BelongsTo $CertStatuses
* @property \Cake\ORM\Association\BelongsTo $Users
*
* @mixin \Cake\ORM\Behavior\TimestampBehavior
*/
class CertsTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('certs');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('CertTemplates', [
'foreignKey' => 'cert_template_id',
'joinType' => 'INNER'
]);
$this->belongsTo('CertStatuses', [
'foreignKey' => 'cert_status_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Users', [
'foreignKey' => 'user_id'
]);
$this->hasMany('CertAlerts', [
'foreignKey' => 'cert_id'
]);
}
public function validationDefault(Validator $validator): \Cake\Validation\Validator
{
$validator
->integer('id')
->allowEmptyFor('id', Validator::EMPTY_ALL, Validator::WHEN_CREATE)
->add('id', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);
$validator
->dateTime('revocation_date')
->allowEmptyDatetime('revocation_date');
$validator
->dateTime('effective_revocation_date')
->allowEmptyDatetime('effective_revocation_date');
$validator
->allowEmptyString('revocation_reason');
$validator
->requirePresence('common_name', 'create')
->notEmptyString('common_name');
$validator
->requirePresence('cert_template_id', 'create')
->notEmptyString('cert_template_id');
$validator
->requirePresence('requester_name', 'create')
->notEmptyString('requester_name');
$validator
->requirePresence('requester_email', 'create')
->notEmptyString('requester_email');
$validator
->allowEmptyString('budget_zi');
$validator
->allowEmptyString('billable');
$validator
->allowEmptyString('key_csr');
$validator
->allowEmptyString('cert_request');
$validator
->allowEmptyString('subject');
$validator
->allowEmptyString('alternative_name');
$validator
->allowEmptyString('serial_no');
$validator
->allowEmptyString('cert');
$validator
->allowEmptyString('ticket');
$validator
->dateTime('request_submission_date')
->allowEmptyDatetime('request_submission_date');
$validator
->dateTime('effective_date')
->requirePresence('effective_date', 'create')
->notEmptyDatetime('effective_date');
$validator
->dateTime('expiry_date')
->requirePresence('expiry_date', 'create')
->notEmptyDatetime('expiry_date');
$validator
->requirePresence('country', 'create')
->notEmptyString('country');
$validator
->requirePresence('organization', 'create')
->notEmptyString('organization');
$validator
->requirePresence('organization_unit', 'create')
->notEmptyString('organization_unit');
$validator
->requirePresence('city', 'create')
->notEmptyString('city');
$validator
->requirePresence('state', 'create')
->notEmptyString('state');
$validator
->allowEmptyString('issued_email');
$validator
->allowEmptyString('san');
$validator
->allowEmptyString('remarks');
return $validator;
}
public function buildRules(RulesChecker $rules): \Cake\ORM\RulesChecker
{
$rules->add($rules->isUnique(['id']));
$rules->add($rules->existsIn(['cert_template_id'], 'CertTemplates'));
$rules->add($rules->existsIn(['cert_status_id'], 'CertStatuses'));
$rules->add($rules->existsIn(['user_id'], 'Users'));
$rules->add($rules->existsIn(['cert_id'], 'CertAlerts'));
return $rules;
}
}
Now I did add the following code which resolved the problem, but feels very much like a hack than an actual solution
if (!array_key_exists('cert_request', $data)) { $data['cert_request'] = ''; }
if (!array_key_exists('ip_address', $data)) { $data['ip_address'] = ''; }
Now the fields do indeed not have a default value in the DB, but I think in the version which is currently in prod (PHP 5.4 and CakePHP 3.3) it just adds empty strings when the values are not present.
I've been reading through the migration notes and didn't find something that would explain why this behavior changed or perhaps it works differently.
There are parts that make it hard to debug (The application was developed on prod relying on external authentication which is not available locally), which is why I might've missed something. To be honest any idea what the problem could be would help.
I feel your pain. I had similar issues because mysql automatically converted such values to empty string and mariadb threw an error.
I think you should ALWAYS have default values in your database tables if you are not sure you have them always set when saving a new database record.
You could also add the logic the set an empty value on "beforeSave" callback in your table model but solving this issue on the database level seems to me to be the cleaner solution.