Laravel Eloquent, how to handle UNIQUE error? - php

I have a MySQL constraint to ensure unique on a composite key. When inserting a new record in my model Foo I get the expected error:
$foo = new Foo(['foo' => 42, 'bar => 1]);
$foo->save();
Error:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '42' for key 'Unique'...
One solution to avoid this error is to query the model before inserting:
if (!Foo::where('foo', 42)->where('bar', 1)->first()) {
$foo = new Foo(['foo' => 42, 'bar => 1]);
$foo->save();
}
Another one would be to catch the exception when preg_match(/UNIQUE/, $e->message) is true.
Is there any better solution?
EDIT
I noticed that in Illuminate\Database\Eloquent\Builder Laravel does the double query anyway which is a bit sad:
public function findOrNew($id, $columns = ['*'])
{
if (! is_null($model = $this->find($id, $columns))) {
return $model;
}
return $this->newModelInstance();
}

In the general case you should be dealing with database errors using the error code and not any regex.
In your particular case pre-querying or using a Laravel method that does that automatically for you, might be preferable if your intention is to overwrite/update existing data.
If you want to generally anticipate an error and handle it you should do something like:
try {
$foo = new Foo(['foo' => 42, 'bar' => 1]);
$foo->save();
} catch (\Exception $e) { // It's actually a QueryException but this works too
if ($e->getCode() == 23000) {
// Deal with duplicate key error
}
}
Refer to https://dev.mysql.com/doc/refman/5.5/en/error-reference.html for an exhaustive list of error codes (but ideally you'd only need to deal with a couple of specific errors and under very specific circumstances.
Lastly the SQL ON DUPLICATE KEY UPDATE might also work for you, however if you are doing this to silently ignore the new values then I suggest you do the error handling instead.

You can use the firstOrCreate method:
$foo = Foo::firstOrCreate(['foo' => 42, 'bar' => 1]);
This will check if the record exists in the database before creating it. If it exists, it will return the record.
For more information: https://laravel.com/docs/5.8/eloquent#inserting-and-updating-models

laravel provides you with the firstOrCreate functions, which first checks if that value exist in the database, then you also have updateOrCreate function to use incase you want to update some value, but most important one, if you want to handle only unique records then check the validation rules, you would do a FormRequest and add unique rule to that field, after that laravel with provide you with error messages, so you handle the error on the client side

The verification need to be done at Controller level (like laravel Validator) and it's better to do a count() instead of the first().
There are different solution depending on what you wanna do when the entity exists in the database. For example you can do updateOrCreate()
$foo = App\Foo::updateOrCreate(['foo' => 42, 'bar' => 1]);
or throw an exception

Related

Unable to insert data in CodeIgniter 4

I am trying out CodeIgniter 4 and I have a problem inserting data.
In my model I only defined the table name, no methods, no nothing. I can display all data in my table but when it comes to inserting data something goes wrong.
My controller code looks like:
$data = [
'sum' => '23',
'type' => 'in',
'name' => 'asd'
];
$expense = new ExpensesModel();
try {
var_dump($expense->insert($data));
} catch (\ReflectionException $e) {
var_dump($e);
}
When I call this endpoint using Postman I get a 500 internal server error.
If I die('asd') before the try-catch I can see the message so this makes me think something happens during the insert method call.
How can I debug this ?
If all you defined within the model was the table name, then you never defined $allowedFields (https://codeigniter.com/user_guide/models/model.html):
This array should be updated with the field names that can be set
during save, insert, or update methods.
You should probably read through that documentation. Moreover, you would troubleshoot these issues by looking at the logs within your project's writable/logs directory, although in this case CI would not consider this a failure as it was you not defining what's allowed into the DB.

Doctrine ODM flush() with unique key

I use doctrine ODM to work with MongoDB. I have documents to save which can duplicate time to time. I need only 1 copy of each event, so I use hashed uniq key to ensure event is only 1.
So I do several ->persist($document);
And when I do ->flush();
I'm getting an exception:
localhost:27017: E11000 duplicate key error index: dbname.event.$eventKey_1 dup key: { : "keyValue" }
And all data never persisted to MongoDB. So question is: is any way to persist uniq data and ignore existing without doing:
try {
->persist();
->flush();
} catch (\Exception $e) {}
for each document?
Thank you.
Updated:
thanks for your answers and your time, but I found exact solution :)
Mongo function insert has an option "ordered: "
https://docs.mongodb.org/manual/reference/method/db.collection.insert/
which allow continue insertion even after errors.
Doctrine use Pecl extension for mongo.
doctrine flush() use this method:
http://www.php.net/manual/en/mongocollection.batchinsert.php
which has option "continueOnError"
So if you do this way:
$documentManager->flush(null, ['continueOnError' => true]);
It will save all documents without errors and skip all with errors. Though it will throw "\MongoDuplicateKeyException". So all you need - catch this exception and handle or simply ignore it (depending on your needs).
Something like this :)
The native Doctrine methods do not support filtering unique values - you need to do this on your own.
To insert those data without any errors you have to do a few things, depending on your entity structure:
Find all existing entities with the unique keys you have
Find unique keys that are duplicated between the entities you are trying to persist
Replace the already existing entities with the entities you found
Persist and flush
There is absolutely no chance to do this without at least one additionally query. If you had the primary key of the existing entities, you could use those to get a reference object. But unfortunately, there is no support for getting references by unique keys according to the doctrine documentation:
http://doctrine-orm.readthedocs.org/en/latest/reference/limitations-and-known-issues.html#join-columns-with-non-primary-keys
It is not possible to use join columns pointing to non-primary keys. Doctrine will think these are the primary keys and create lazy-loading proxies with the data, which can lead to unexpected results. Doctrine can for performance reasons not validate the correctness of this settings at runtime but only through the Validate Schema command.
I was unable to get the ['continueOnError' => true] to work, so solved it differently:
$collection = $documentManager->getDocumentCollection(Content::class);
try {
$collection->insertMany($arrayData, ['ordered' => false]);
} catch (BulkWriteException $exception) {
// ignore duplicate key errors
if (false === strpos($exception->getMessage(), 'E11000 duplicate key error')) {
throw $exception;
}
}
You can just do: Search your existing id edit it and save..
With Doctrine + symfony:
$Document = $EntityManager->getRepository("ExpBundle:Document")->find(123);
$Document->setTitile("Doc");
$EntityManager->flush();
With Doctrine
$Document = $EntityManager->find('Document', 1234);
$Document->setTitle("edited");
$EntityManager->flush();

Save model in Yii twice - getting "Integrity constraint violation: 1062 Duplicate entry"

I'm trying to save newly created yii model twice - first to get auto-incremented id. And the second time to save that id-related stuff:
$node = new Node;
$node->attributes = $attrs;
$node->save(); // now I have 'id'
$node->vector = calcVector($node->id); // vector is based on 'id'
$node->save();
The second save (edit: error was thrown elsewhere) throws this error: Integrity constraint violation: 1062 Duplicate entry. The expected behavior is to simply update the already saved model.
What is the right way to save it second time?
(I could do $node = Node::model()->findByPk($node->id);, but that doesn't seem right)
just set
$node->isNewRecord = false;
then
$node->save();
cheers
Uh, so apparently the problem was not in what I describe above.
Saving twice is working as expected - 1st call inserts, 2nd call updates.
The problem was probably that I was saving the model in beforeSave(). I've had a complicated and confusing logic in there, didn't realize what's happening..
I had a somewhat similar situation where I needed to save a model to database multiple times. I accomplished it by simply instantiating a model after saving it:
foreach ($partsIdArray as $id)
{
$model->load(Yii::$app->request->post()); // loading form values
$model->part_id = $id;
$model->save();
$model = new \backend\models\Abc();
}

how to allow duplicate entries in database in Zend Framework 2?

recently I've created a simple registration form and when i try to save the data, if i enter an existing username or email i get an error saying that the query is stopped becasue the field already exists
In some cases this is a great feature, and i could use it in the email case.
Im not sure if i have to set this in the form validators or in the config.php or in my module.
here is my save method:
public function saveUser(User $user)
{
$data = array(
'username' => $user->username,
'email' => $user->email,
'password' => $user->password,
);
$id = (int) $user->user_id;
if ($id == 0) {
$this->insert($data);
} elseif ($this->getUser($id)) {
$this->update(
$data,
array(
'user_id' => $user_id,
)
);
} else {
throw new \Exception('Form id does not exist');
}
}
and here is the short error:
Statement could not be executed
...
QLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'admin' for key 'username'
....
like i said, i could use this error for the email, because i want unique email, but im not sure how to catch this error and display it in a nicer format, maybe like a validation error.
any ideas on this issue?
thanks
If you are using the Zend_Form to create the forms you can use the Zend_Validate_Db_NoRecordExists to check if the email already exists or not.
This is error returned by your SQL server. To get rid of this you need to remove UNIQUE KEY from your field username.
As for displaying "nice error message" if user email is duplicate, I'm afraid the only choice you have is to check its existence before executing INSERT.
I once had database model which was throwing Database_UniqueKey_Exception( $field) which allowed you to do this in quite stylish fashion, but AFAIK Zend doesn't support special handling for unique key issues and you have to either parse error message (I wouldn't go there) or check it in advance.
Here's an example on how to achieve what you want. Zend Framework: Validate duplicate database entry
It uses the MySQL PDOException code: 23000 to check if a duplicate entry exception occurred and attaches the error message to the form. This way you won't need to make an extra database query in order to check for duplicate username entry.
Hope this helps.

How to get primary key of Database_Exception [ 1062 ] ("duplicate entry") in Kohana 3.2?

Lets say that I have a table with column called a. It has index UNIQUE KEY on it.
In ORM model, I try to insert into that table. This is a way to catch Database_Exception [ 1062 ] that occurs when user tries to insert something in column a that's not unique:
function save(Validation $validation = null) {
try {
parent::save($validation);
}
catch (Database_Exception $exception) {
if ($exception->getCode() === 1062) {
// PK?
}
}
}
Now I'm trying to get primary key of entry that already have that content what I tried to duplicate. Is it possible without any more SQL queries? I hope that primary key of that row is returned somewhere.
Sorry about non-sense, but it was kinda hard to explain. Thanks in an advice!
NO, this is not posible.
This is not expected also to have the primary key for that duplicate entry from a exception.
It is underlying database which throw this exception.
It could be possible if database could report that message somehow. But it will lead to so many similar cases where much more info is needed. And all the database vendor needs to confirm this which is impossible, I believe.

Categories