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();
Related
I have an entity Key (Not the real name, I know Key is forbidden) and I need, in a loop, get a Key with state=1, and change it to state=2. This is my script :
/* Each object */
for ($i=0; $i < $order->getQuantity(); $i++) {
/* get available key */
$key = $this->getDoctrine()->getRepository('AppBundle:Key')->findOneBy(array('state' => 1));
$key->setState(2); // On la rend active
$this->_em()->persist($key);
}
}
My probleme is with this line : $key = $this->getDoctrine()->getRepository('AppBundle:Key')->findOneBy(array('state' => 1));
Doctrine always get the same first key with state=1. If I flush directly in the loop it's ok, but I can have a very big loop and I don't want to flush XXXX times.
Is there a way to don't get already persisted entity ? How can I say to Doctrine to get a Key with state=1 ONLY if I don't already persisted ?
Thanks !
Why don't you do this:
$keys = $this->getDoctrine()->getRepository('AppBundle:Key')->findBy(array('state' => 1));
foreach($keys as $key) {
$key->setState(2);
$this->_em()->persist($key);
}
$this->_em()->flush();
Thereby each key will only be persisted once and because persisting things is symfony logic only you have only one DB write action during the flush-function where all persisted items will be stored
In addition to retrieving and looping over your entities, you can also use DQL (unless I am missing context from your question that precludes this).
For example:
$dql = 'UPDATE AppBundle:Key k SET k.state = 2 WHERE k.state = 1';
$query = $this->_em->createQuery($dql);
$result = $query->getResult();
This is untested obviously. It's been a while since I wrote DQL so you might want to consult the docs. Hope this helps :)
Persisting means "Hey Doctrine, let's be aware of that entity instance!".
It's used (as you surely already know) when you create a new entity instance ($key = new Key();), and then you want Doctrine to be aware of it ($em->persist($key);) to be able to add a new record in the database on flush ($em->flush()).
All entity instances retrieved with Doctrine are already persisted (Doctrine is already aware of them).
So, in your code, the persist call is useless. And as you don't flush, the database is not updated.
Then, in the next loop, when you request from the database (->findOneBy(...)), you will get again the same entity instance, with state still equals to 1.
Finally, to answer your questions "Is there a way to don't get already persisted entity ? How can I say to Doctrine to get a Key with state=1 ONLY if I don't already persisted ?":
No, it's just impossible.
I am importing users from an excel file. I have created entity manager:
$em = $this->getDoctrine()->getManager();
This is my loop:
...
for ($i=2; $i<=count($usersdatas); $i++) { // $userdatas: an array of users imported from excel
$nuser = new User();
$nuser->setEmail($usersdatas[$i]['A']); // email is unique in db
$nuser->setFname($usersdatas[$i]['B']);
$nuser->setLname($usersdatas[$i]['C']);
$nuser->addChannel($channel); //ManytoMany relation
$em->persist($nuser);
$em->flush();
}
...
Email field is unique in db and I do not want to check duplicate before flush (db validation)
I want to flush one by one for log reasons (not batch insert).
All my other codes and files (entity, services, configurations, ...) are correct and has no errors.
The first problem is if one of the rows has an email that exist in db (duplicate), the loop breaks by an exception from db.
I have solved the problem with this simple change:
...
try {
$em->persist($nuser);
$em->flush();
} catch (\Exception $e) {
...
}
...
The next problem is the "The EntityManager is closed" exception. If one email is duplicate, EntityManager ($em) will close automatically.
I have solved this by creating the $em before try/catch:
...
if (!$em->isOpen()) {
$em = $em->create(
$em->getConnection(), $em->getConfiguration());
}
...
Everything is OK. But I have a big problem with adding the channel to user:
...
$nuser->addChannel($channel); //ManytoMany relation
...
$channel is not a new one. But after closing and creating the EntityManager, the system thinks it is new on line persist (The $channel will successfully add if there is no errors):
ORMInvalidArgumentException {#1400 ▼
#message: "A new entity was found through the relationship 'My\UserBundle\Entity\User#channels' that was not configured to cascade persist operations for entity: My\PostBundle\Entity\Channel#000000002768057d0000000046371762. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'My\PostBundle\Entity\Channel#__toString()' to get a clue."
#code: 0
#file: "...\vendor\doctrine\orm\lib\Doctrine\ORM\ORMInvalidArgumentException.php"
#line: 91
-trace: array:13 [▶]
}
All my problems are from recreating EntityManager.
I think its a bug. Validating data in database side (Unique, Null, Foreign Keys, ...) is common. Closing the EntityManager after db errors means we can not communicate with db after any error.
I have 2 questions:
Is there any way to prevent EntityManager close?!
Can I use DBAL Doctrine to import my users? (users may have duplicated emails)
Exceptions are for exceptional situations. They're designed to let you clean up and then return a friendly error. They're not designed for validation.
How you're using them here is to say "Is it possible to add this user?" and if not, restart the database. That is, stop the entity manager manager from throwing exceptions, instead of trying to carry on after it has thrown an exception.
So if you've identified that the entity manager is throwing an exception and closing when you try to add a duplicate user, then check whether the user is a duplicate before trying to add them.
However, you said above that you don't want to use this approach. In that case, you will need to write your own SQL queries and can use DBAL (or just raw PDOs) and handle the SQL responses yourself.
i got some stuck when accessing a yii's web application. I have configured as the same as the owner's setting, but while i tried to access, i got an error "Column must be either a string or an array". How could i solve it? Thanks in advance..
When reporting error messages, it helps to have the precise error message. The actual error message is: "Column name must be either a string or an array". With an exact string you can search the framework files to find where it is mentioned.
Looks like some method somewhere is passing an invalid column name to createInCondition method of CDbCommandBuilder.
See line 722: https://github.com/yiisoft/yii/blob/1.1.13/framework/db/schema/CDbCommandBuilder.php
Looking at a couple instances where that method is called, I would guess that you have a database table without a primary key somewhere. That is one possible explanation for the problem. Other explanations will require a lot more details on your part.
Provide the stack trace that the error page provides you with when in debug mode along with your table schema.
This happens when you don't have a primary key in your table and you try to do an update. I got this problem because I had a composite primary key in my table. I was being handled well on all operations until I wanted to update a model.
Just add an int primary key, call it 'id' to your table with auto increment. It should do the trick.
Be sure to disable schema caching (if you're using that) before you test this. The change wont take effect until your schema cache expires.
Maybe you do not have primary key in your table. If you use the method $model->save() to save or use method $model->update() ($model is CActiveRecord instance), you will get this error.
Because the method update in CActiveRecord using Primary key to update (Read more here
)
Source Code: framework/db/ar/CActiveRecord.php#1115
if($this->_pk===null)
$this->_pk=$this->getPrimaryKey();
$this->updateByPk($this->getOldPrimaryKey(),$this->getAttributes($attributes));
$this->_pk=$this->getPrimaryKey();
You can use method updateAll() instead of update() or updateByPk()
Take a look this link
http://www.yiiframework.com/forum/index.php/topic/3887-cdbexception-column-name-must-be-either-a-string-or-an-array/
It seems your table doesn't have a primary key or the primary key doesn't well restored which usually caused by corrupt back up file.
If you forgot add return value you will have error you showed. Simple example, your model with such method will return error on PK
...
public function relations()
{
}
...
Your have to add return value.
/**
* #return array
*/
public function relations()
{
return array();
}
If you are not using such methods you should delete them, or add 'default return values'. Otherwise it gives errors the same as it was primary key or other DB issues (because model read invalid data and didn't all things it should).
i'm creating my Doctrine record this way:
$user = new User();
$user->fromArray($_POST);
$user->save();
This throws an exception:
Uncaught exception 'Doctrine_Validator_Exception' with message 'Validation failed in class User 1 field had validation error: * 1 validator failed on dni (type)
Of course, the "dni" field is of type: integer, and the HTTP POST has all the values as strings. This is what makes the validation fail. The only way of passing the validation is doing this:
$_POST['dni'] = (int) $_POST['dni'];
But it "feels" wrong.
As a note, I'm working with Doctrine integrated into CodeIgniter. This didn't ever happened to me when worked with Symfony.
Many thanks.
This is old but unanswered so here it is:
$manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, VALIDATE_ALL & ~VALIDATE_TYPES);
You can instruct Doctrine to validate all or no combination of: Types (your issue), Length, Constraints, All and None.
You can do it on the global level.
Probably also on the connection level.
And definetly per table but I could not find it on the doctrine orm documentation.
and you don't need to worry about having a wrong data type inserted in the database, the db won't let you and that's outside of doctrine's hand.
What is the best way in PHP to handle foreign key exceptions on a mysql database? Is there a mysql class that can be used to simplify any code?
Ideally, what I want to do, as an example, is to try to delete a record where it is the foreign key parent to any number of child tables. The foreign key throws the exception, so then I would like to be able to look at each foreign key table and test it, giving meaningful feedback on the tables and number of records causing the exception. This would then be returned as the error so the end user can reference and delete the offending records.
The way I handle this is to set up my database wrapper class to always throw an exception when you encounter a database error. So, for instance, I might have a class called MySQL with the following functions:
public function query($query_string)
{
$this->queryId = mysql_query($query_string,$this->connectionId);
if (! $this->queryId) {
$this->_throwException($query_string);
}
return $this->queryId;
}
private function _throwException($query = null)
{
$msg = mysql_error().". Query was:\n\n".$query.
"\n\nError number: ".mysql_errno();
throw new Exception($msg,mysql_errno());
}
Any time a query fails, a regular PHP exception is thrown. Note that I would throw these from within other places too, like a connect() function or a selectDb() function, depending on whether the operation succeeded or not.
With that set up, you're good to go. Any place you expect that you might need to be handling a database error, do something like the following:
//assume $db has been set up to be an instance of the MySQL class
try {
$db->query("DELETE FROM parent WHERE id=123");
} catch (Exception $e) {
//uh-oh, maybe a foreign key restraint failed?
if ($e->getCode() == 'mysql foreign key error code') {
//yep, it failed. Do some stuff.
}
}
Edit
In response to the poster's comment below, you have some limited information available to you to help diagnose a foreign key issue. The error text created by a failed foreign key restraint and returned by mysql_error() looks something like this:
Cannot delete or update a parent row:
a foreign key constraint fails
(`dbname`.`childtable`, CONSTRAINT `FK_name_1` FOREIGN KEY
(`fieldName`) REFERENCES `parenttable` (`fieldName`));
If your foreign keys are complex enough that you can't be sure what might cause a foreign key error for a given query, then you could probably parse this error text to help figure it out. The command SHOW ENGINE INNODB STATUS returns a more detailed result for the latest foreign key error as well.
Otherwise, you're probably going to have to do some digging yourself. The following query will give you a list of foreign keys on a given table, which you can examine for information:
select * from information_schema.table_constraints
WHERE table_schema=schema() AND table_name='table_name';
Unfortunately, I don't think there's a magic bullet to your solution other than examining the errors and constraints very carefully.
I think the best bet would be for you to do a transaction. That way, the insert will always be valid, or not done at all. That can return an error message that you can work with as well. This will prevent you from having to manually check every table - the db does it for you.