Skip to content

Instantly share code, notes, and snippets.

@Koc
Created November 6, 2012 22:25
Show Gist options
  • Save Koc/4028068 to your computer and use it in GitHub Desktop.
Save Koc/4028068 to your computer and use it in GitHub Desktop.
Doctrine2 bulk inserts
<?php
use Doctrine\ORM\Persisters;
class DoctrineHelper
{
protected $entityManagers = array();
public function bulkCommit(EntityManager $em, array $entities)
{
$oid = spl_object_hash($em);
if (!isset($this->entityManagers[$oid])) {
$_em = \Doctrine\ORM\EntityManager::create($em->getConnection(), $em->getConfiguration(), $em->getEventManager());
$uow = new HackedUnitOfWork($_em);
$reflectionProperty = new \ReflectionProperty(get_class($_em), 'unitOfWork');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($_em, $uow);
$this->entityManagers[$oid] = $_em;
}
foreach ($entities as $entity) {
$this->entityManagers[$oid]->persist($entity);
}
$this->entityManagers[$oid]->getUnitOfWork()->commit($entities);
}
}
class HackedUnitOfWork extends \Doctrine\ORM\UnitOfWork
{
private $persisters = array();
private $em;
/**
* Initializes a new UnitOfWork instance, bound to the given EntityManager.
*
* @param \Doctrine\ORM\EntityManager $em
*/
public function __construct(\Doctrine\ORM\EntityManager $em)
{
$this->em = $em;
parent::__construct($em);
}
/**
* Gets the EntityPersister for an Entity.
*
* @param string $entityName The name of the Entity.
*
* @return \Doctrine\ORM\Persisters\BasicEntityPersister
*/
public function getEntityPersister($entityName)
{
if (isset($this->persisters[$entityName])) {
return $this->persisters[$entityName];
}
$class = $this->em->getClassMetadata($entityName);
switch (true) {
case ($class->isInheritanceTypeNone()):
$persister = new HackedBasicEntityPersistener($this->em, $class);
break;
case ($class->isInheritanceTypeSingleTable()):
$persister = new Persisters\SingleTablePersister($this->em, $class);
break;
case ($class->isInheritanceTypeJoined()):
$persister = new Persisters\JoinedSubclassPersister($this->em, $class);
break;
default:
$persister = new Persisters\UnionSubclassPersister($this->em, $class);
}
$this->persisters[$entityName] = $persister;
return $this->persisters[$entityName];
}
}
class HackedBasicEntityPersistener extends Persisters\BasicEntityPersister
{
private $_insertSqlArray;
/**
* Executes all queued entity insertions and returns any generated post-insert
* identifiers that were created as a result of the insertions.
*
* If no inserts are queued, invoking this method is a NOOP.
*
* @return array An array of any generated post-insert IDs. This will be an empty array
* if the entity class does not use the IDENTITY generation strategy.
*/
public function executeInserts()
{
if ( ! $this->_queuedInserts) {
return;
}
$postInsertIds = array();
list($insertSql, $placeholders) = $this->_getInsertSQL();
$tableName = $this->_class->getTableName();
$insertDataArray = array();
foreach ($this->_queuedInserts as $entity) {
$insertData = $this->_prepareInsertData($entity);
if (isset($insertData[$tableName])) {
$insertDataArray[] = $insertData[$tableName];
}
}
$this->_conn->commit();
$stmt = $this->_conn->prepare($insertSql . implode(', ', array_fill(0, count($insertDataArray), $placeholders)));
$paramIndex = 1;
foreach ($insertDataArray as $row) {
foreach ($row as $column => $value) {
$stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$column]);
}
}
$stmt->execute();
$this->_conn->beginTransaction();
$stmt->closeCursor();
$this->_queuedInserts = array();
return $postInsertIds;
}
/**
* Gets the INSERT SQL used by the persister to persist a new entity.
*
* @return string
*/
protected function _getInsertSQL()
{
if ($this->_insertSqlArray === null) {
$insertSql = '';
$columns = $this->_getInsertColumnList();
if (empty($columns)) {
$insertSql = $this->_platform->getEmptyIdentityInsertSQL(
$this->quoteStrategy->getTableName($this->_class, $this->_platform),
$this->quoteStrategy->getColumnName($this->_class->identifier[0], $this->_class, $this->_platform)
);
} else {
$columns = array_unique($columns);
$values = array();
foreach ($columns as $column) {
$placeholder = '?';
if (isset($this->_class->fieldNames[$column]) &&
isset($this->_columnTypes[$this->_class->fieldNames[$column]]) &&
isset($this->_class->fieldMappings[$this->_class->fieldNames[$column]]['requireSQLConversion'])) {
$type = Type::getType($this->_columnTypes[$this->_class->fieldNames[$column]]);
$placeholder = $type->convertToDatabaseValueSQL('?', $this->_platform);
}
$values[] = $placeholder;
}
//TODO: use different strategies for other databases platforms
$insertSql = 'INSERT INTO ' . $this->quoteStrategy->getTableName($this->_class, $this->_platform)
. ' (' . implode(', ', $columns) . ') VALUES ';
}
$this->_insertSqlArray = array($insertSql, '(' . implode(', ', $values) . ')');
}
return $this->_insertSqlArray;
}
}
<?php
namespace Ololo\SandboxBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Ololo\SandboxBundle\Entity\DemoEntity;
class DoctrineMultiInsertsCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('ololo:sandbox:doctrine-multi-inserts');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$em = $this->getContainer()->get('doctrine')->getEntityManager(); /* @var $em \Doctrine\ORM\EntityManager */
$conn = $em->getConnection();
$doctrineHelper = new DoctrineHelper();
$conn->executeQuery('TRUNCATE TABLE demo_entity');
$entitiesToCommit = array();
$start = microtime(true);
for ($i = 1; $i <= 5000; $i++) {
$entity = new DemoEntity();
$entity->setId($i);
$entity->setTitle(sprintf('title-%d', $i));
$entity->setDescription(sprintf('description-%s', $i));
$entitiesToCommit[] = $entity;
if ($i % 25 == 0) {
$doctrineHelper->bulkCommit($em, $entitiesToCommit);
$entitiesToCommit = array();
}
}
if ($entitiesToCommit) {
$doctrineHelper->bulkCommit($em, $entitiesToCommit);
}
$output->writeln(sprintf('Completed in %0.3f s', microtime(true) - $start));
}
}
@inmarelibero
Copy link

this piece of code is very interesting to me.

it's returning to me the error:

[ErrorException]                                                                                                                                 
  Notice: Undefined index: 000000007b5eb063000000001558936f in doctrine/orm/lib/Doctrine/ORM/UnitOfWork.  
  php line 2762

I'm on Symfony 2.2.1. Any advice?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment