Created
November 6, 2012 22:25
-
-
Save Koc/4028068 to your computer and use it in GitHub Desktop.
Doctrine2 bulk inserts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
this piece of code is very interesting to me.
it's returning to me the error:
I'm on Symfony 2.2.1. Any advice?