/** * @dataProvider lockDataProvider */ public function testLock($isVersioned, $expectsException) { $object = new VersionedEntity(); $em = $this->getMockBuilder('Doctrine\\ORM\\EntityManager')->disableOriginalConstructor()->setMethods(array('lock'))->getMock(); $modelManager = $this->getMockBuilder('Sonata\\DoctrineORMAdminBundle\\Model\\ModelManager')->disableOriginalConstructor()->setMethods(array('getMetadata', 'getEntityManager'))->getMock(); $modelManager->expects($this->any())->method('getEntityManager')->will($this->returnValue($em)); $metadata = $this->getMetadata(get_class($object), $isVersioned); $modelManager->expects($this->any())->method('getMetadata')->will($this->returnValue($metadata)); if ($expectsException) { $em->expects($this->once())->method('lock')->will($this->throwException(OptimisticLockException::lockFailed($object))); $this->setExpectedException('Sonata\\AdminBundle\\Exception\\LockException'); } $modelManager->lock($object, 123); }
/** * {@inheritDoc} */ public function find($id, $lockMode = LockMode::NONE, $lockVersion = null) { // Check identity map first if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) { if (!$entity instanceof $this->_class->name) { return null; } if ($lockMode != LockMode::NONE) { $this->_em->lock($entity, $lockMode, $lockVersion); } return $entity; // Hit! } if (!is_array($id) || count($id) <= 1) { // @todo FIXME: Not correct. Relies on specific order. $value = is_array($id) ? array_values($id) : array($id); $id = array_combine($this->_class->identifier, $value); } $id = $this->addInstanceFilter($id); if ($lockMode == LockMode::NONE) { return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id); } else { if ($lockMode == LockMode::OPTIMISTIC) { if (!$this->_class->isVersioned) { throw OptimisticLockException::notVersioned($this->_entityName); } $entity = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id); $this->_em->getUnitOfWork()->lock($entity, $lockMode, $lockVersion); return $entity; } else { if (!$this->_em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id, null, null, array(), $lockMode); } } }
/** * Performs an UPDATE statement for an entity on a specific table. * The UPDATE can optionally be versioned, which requires the entity to have a version field. * * @param object $entity The entity object being updated. * @param string $quotedTableName The quoted name of the table to apply the UPDATE on. * @param array $updateData The map of columns to update (column => value). * @param boolean $versioned Whether the UPDATE should be versioned. */ protected final function _updateTable($entity, $quotedTableName, array $updateData, $versioned = false) { $set = $params = $types = array(); foreach ($updateData as $columnName => $value) { if (isset($this->_class->fieldNames[$columnName])) { $set[] = $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform) . ' = ?'; } else { $set[] = $columnName . ' = ?'; } $params[] = $value; $types[] = $this->_columnTypes[$columnName]; } $where = array(); $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); foreach ($this->_class->identifier as $idField) { if (isset($this->_class->associationMappings[$idField])) { $targetMapping = $this->_em->getClassMetadata($this->_class->associationMappings[$idField]['targetEntity']); $where[] = $this->_class->associationMappings[$idField]['joinColumns'][0]['name']; $params[] = $id[$idField]; $types[] = $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type']; } else { $where[] = $this->_class->getQuotedColumnName($idField, $this->_platform); $params[] = $id[$idField]; $types[] = $this->_class->fieldMappings[$idField]['type']; } } if ($versioned) { $versionField = $this->_class->versionField; $versionFieldType = $this->_class->fieldMappings[$versionField]['type']; $versionColumn = $this->_class->getQuotedColumnName($versionField, $this->_platform); if ($versionFieldType == Type::INTEGER) { $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; } else { if ($versionFieldType == Type::DATETIME) { $set[] = $versionColumn . ' = CURRENT_TIMESTAMP'; } } $where[] = $versionColumn; $params[] = $this->_class->reflFields[$versionField]->getValue($entity); $types[] = $this->_class->fieldMappings[$versionField]['type']; } $sql = "UPDATE {$quotedTableName} SET " . implode(', ', $set) . ' WHERE ' . implode(' = ? AND ', $where) . ' = ?'; $result = $this->_conn->executeUpdate($sql, $params, $types); if ($versioned && !$result) { throw OptimisticLockException::lockFailed($entity); } }
/** * Finds an Entity by its identifier. * * @param string $entityName The class name of the entity to find. * @param mixed $id The identity of the entity to find. * @param integer|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants * or NULL if no specific lock mode should be used * during the search. * @param integer|null $lockVersion The version of the entity to find when using * optimistic locking. * * @return object|null The entity instance or NULL if the entity can not be found. * * @throws OptimisticLockException * @throws ORMInvalidArgumentException * @throws TransactionRequiredException * @throws ORMException */ public function find($entityName, $id, $lockMode = null, $lockVersion = null) { $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); if (is_object($id) && $this->metadataFactory->hasMetadataFor(ClassUtils::getClass($id))) { $id = $this->unitOfWork->getSingleIdentifierValue($id); if ($id === null) { throw ORMInvalidArgumentException::invalidIdentifierBindingEntity(); } } if (!is_array($id)) { $id = array($class->identifier[0] => $id); } $sortedId = array(); foreach ($class->identifier as $identifier) { if (!isset($id[$identifier])) { throw ORMException::missingIdentifierField($class->name, $identifier); } $sortedId[$identifier] = $id[$identifier]; } $unitOfWork = $this->getUnitOfWork(); // Check identity map first if (($entity = $unitOfWork->tryGetById($sortedId, $class->rootEntityName)) !== false) { if (!$entity instanceof $class->name) { return null; } switch (true) { case LockMode::OPTIMISTIC === $lockMode: $this->lock($entity, $lockMode, $lockVersion); break; case LockMode::NONE === $lockMode: case LockMode::PESSIMISTIC_READ === $lockMode: case LockMode::PESSIMISTIC_WRITE === $lockMode: $persister = $unitOfWork->getEntityPersister($class->name); $persister->refresh($sortedId, $entity, $lockMode); break; } return $entity; // Hit! } $persister = $unitOfWork->getEntityPersister($class->name); switch (true) { case LockMode::OPTIMISTIC === $lockMode: if (!$class->isVersioned) { throw OptimisticLockException::notVersioned($class->name); } $entity = $persister->load($sortedId); $unitOfWork->lock($entity, $lockMode, $lockVersion); return $entity; case LockMode::NONE === $lockMode: case LockMode::PESSIMISTIC_READ === $lockMode: case LockMode::PESSIMISTIC_WRITE === $lockMode: if (!$this->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } return $persister->load($sortedId, null, null, array(), $lockMode); default: return $persister->loadById($sortedId); } }
/** * Acquire a lock on the given entity. * * @param object $entity * @param int $lockMode * @param int $lockVersion * * @return void * * @throws ORMInvalidArgumentException * @throws TransactionRequiredException * @throws OptimisticLockException */ public function lock($entity, $lockMode, $lockVersion = null) { if ($entity === null) { throw new \InvalidArgumentException("No entity passed to UnitOfWork#lock()."); } if ($this->getEntityState($entity, self::STATE_DETACHED) != self::STATE_MANAGED) { throw ORMInvalidArgumentException::entityNotManaged($entity); } $class = $this->em->getClassMetadata(get_class($entity)); switch (true) { case LockMode::OPTIMISTIC === $lockMode: if (!$class->isVersioned) { throw OptimisticLockException::notVersioned($class->name); } if ($lockVersion === null) { return; } $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); if ($entityVersion != $lockVersion) { throw OptimisticLockException::lockFailedVersionMismatch($entity, $lockVersion, $entityVersion); } break; case LockMode::NONE === $lockMode: case LockMode::PESSIMISTIC_READ === $lockMode: case LockMode::PESSIMISTIC_WRITE === $lockMode: if (!$this->em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } $oid = spl_object_hash($entity); $this->getEntityPersister($class->name)->lock(array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), $lockMode); break; default: // Do nothing } }
/** * {@inheritdoc} */ public function walkSelectStatement(AST\SelectStatement $AST) { $sql = $this->walkSelectClause($AST->selectClause); $sql .= $this->walkFromClause($AST->fromClause); $sql .= $this->walkWhereClause($AST->whereClause); $sql .= $AST->groupByClause ? $this->walkGroupByClause($AST->groupByClause) : ''; $sql .= $AST->havingClause ? $this->walkHavingClause($AST->havingClause) : ''; if (($orderByClause = $AST->orderByClause) !== null) { $sql .= $AST->orderByClause ? $this->walkOrderByClause($AST->orderByClause) : ''; } else { if (($orderBySql = $this->_generateOrderedCollectionOrderByItems()) !== '') { $sql .= ' ORDER BY ' . $orderBySql; } } $sql = $this->platform->modifyLimitQuery($sql, $this->query->getMaxResults(), $this->query->getFirstResult()); if (($lockMode = $this->query->getHint(Query::HINT_LOCK_MODE)) !== false) { switch ($lockMode) { case LockMode::PESSIMISTIC_READ: $sql .= ' ' . $this->platform->getReadLockSQL(); break; case LockMode::PESSIMISTIC_WRITE: $sql .= ' ' . $this->platform->getWriteLockSQL(); break; case LockMode::OPTIMISTIC: foreach ($this->selectedClasses as $selectedClass) { if (!$selectedClass['class']->isVersioned) { throw \Doctrine\ORM\OptimisticLockException::lockFailed($selectedClass['class']->name); } } break; case LockMode::NONE: break; default: throw \Doctrine\ORM\Query\QueryException::invalidLockMode(); } } return $sql; }
/** * Acquire a lock on the given entity. * * @param object $entity * @param int $lockMode * @param int $lockVersion */ public function lock($entity, $lockMode, $lockVersion = null) { if ($this->getEntityState($entity) != self::STATE_MANAGED) { throw new InvalidArgumentException("Entity is not MANAGED."); } $entityName = get_class($entity); $class = $this->em->getClassMetadata($entityName); if ($lockMode == \Doctrine\DBAL\LockMode::OPTIMISTIC) { if (!$class->isVersioned) { throw OptimisticLockException::notVersioned($entityName); } if ($lockVersion != null) { $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); if ($entityVersion != $lockVersion) { throw OptimisticLockException::lockFailedVersionMissmatch($entity, $lockVersion, $entityVersion); } } } else { if (in_array($lockMode, array(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE))) { if (!$this->em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } $oid = spl_object_hash($entity); $this->getEntityPersister($class->name)->lock(array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), $lockMode); } } }
/** * Performs an UPDATE statement for an entity on a specific table. * The UPDATE can optionally be versioned, which requires the entity to have a version field. * * @param object $entity The entity object being updated. * @param string $quotedTableName The quoted name of the table to apply the UPDATE on. * @param array $updateData The map of columns to update (column => value). * @param boolean $versioned Whether the UPDATE should be versioned. * * @return void * * @throws \Doctrine\ORM\ORMException * @throws \Doctrine\ORM\OptimisticLockException */ protected final function updateTable($entity, $quotedTableName, array $updateData, $versioned = false) { $set = array(); $types = array(); $params = array(); foreach ($updateData as $columnName => $value) { $placeholder = '?'; $column = $columnName; switch (true) { case isset($this->class->fieldNames[$columnName]): $fieldName = $this->class->fieldNames[$columnName]; $column = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform); if (isset($this->class->fieldMappings[$fieldName]['requireSQLConversion'])) { $type = Type::getType($this->columnTypes[$columnName]); $placeholder = $type->convertToDatabaseValueSQL('?', $this->platform); } break; case isset($this->quotedColumns[$columnName]): $column = $this->quotedColumns[$columnName]; break; } $params[] = $value; $set[] = $column . ' = ' . $placeholder; $types[] = $this->columnTypes[$columnName]; } $where = array(); $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); foreach ($this->class->identifier as $idField) { if ( ! isset($this->class->associationMappings[$idField])) { $params[] = $identifier[$idField]; $types[] = $this->class->fieldMappings[$idField]['type']; $where[] = $this->quoteStrategy->getColumnName($idField, $this->class, $this->platform); continue; } $params[] = $identifier[$idField]; $where[] = $this->class->associationMappings[$idField]['joinColumns'][0]['name']; $targetMapping = $this->em->getClassMetadata($this->class->associationMappings[$idField]['targetEntity']); switch (true) { case (isset($targetMapping->fieldMappings[$targetMapping->identifier[0]])): $types[] = $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type']; break; case (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])): $types[] = $targetMapping->associationMappings[$targetMapping->identifier[0]]['type']; break; default: throw ORMException::unrecognizedField($targetMapping->identifier[0]); } } if ($versioned) { $versionField = $this->class->versionField; $versionFieldType = $this->class->fieldMappings[$versionField]['type']; $versionColumn = $this->quoteStrategy->getColumnName($versionField, $this->class, $this->platform); $where[] = $versionColumn; $types[] = $this->class->fieldMappings[$versionField]['type']; $params[] = $this->class->reflFields[$versionField]->getValue($entity); switch ($versionFieldType) { case Type::INTEGER: $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; break; case Type::DATETIME: $set[] = $versionColumn . ' = CURRENT_TIMESTAMP'; break; } } $sql = 'UPDATE ' . $quotedTableName . ' SET ' . implode(', ', $set) . ' WHERE ' . implode(' = ? AND ', $where) . ' = ?'; $result = $this->conn->executeUpdate($sql, $params, $types); if ($versioned && ! $result) { throw OptimisticLockException::lockFailed($entity); } }
/** * {@inheritdoc} */ public function walkSelectStatement(AST\SelectStatement $AST) { $limit = $this->query->getMaxResults(); $offset = $this->query->getFirstResult(); $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE); $sql = $this->walkSelectClause($AST->selectClause) . $this->walkFromClause($AST->fromClause) . $this->walkWhereClause($AST->whereClause); if ($AST->groupByClause) { $sql .= $this->walkGroupByClause($AST->groupByClause); } if ($AST->havingClause) { $sql .= $this->walkHavingClause($AST->havingClause); } if ($AST->orderByClause) { $sql .= $this->walkOrderByClause($AST->orderByClause); } if (!$AST->orderByClause && ($orderBySql = $this->_generateOrderedCollectionOrderByItems())) { $sql .= ' ORDER BY ' . $orderBySql; } if ($limit !== null || $offset !== null) { $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset); } if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) { return $sql; } if ($lockMode === LockMode::PESSIMISTIC_READ) { return $sql . ' ' . $this->platform->getReadLockSQL(); } if ($lockMode === LockMode::PESSIMISTIC_WRITE) { return $sql . ' ' . $this->platform->getWriteLockSQL(); } if ($lockMode !== LockMode::OPTIMISTIC) { throw QueryException::invalidLockMode(); } foreach ($this->selectedClasses as $selectedClass) { if (!$selectedClass['class']->isVersioned) { throw OptimisticLockException::lockFailed($selectedClass['class']->name); } } return $sql; }
/** * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. * * @return string The SQL. */ public function walkSelectStatement(AST\SelectStatement $AST) { $sql = $this->walkSelectClause($AST->selectClause); $sql .= $this->walkFromClause($AST->fromClause); if (($whereClause = $AST->whereClause) !== null) { $sql .= $this->walkWhereClause($whereClause); } else { if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') { $sql .= ' WHERE ' . $discSql; } } $sql .= $AST->groupByClause ? $this->walkGroupByClause($AST->groupByClause) : ''; $sql .= $AST->havingClause ? $this->walkHavingClause($AST->havingClause) : ''; if (($orderByClause = $AST->orderByClause) !== null) { $sql .= $AST->orderByClause ? $this->walkOrderByClause($AST->orderByClause) : ''; } else { if (($orderBySql = $this->_generateOrderedCollectionOrderByItems()) !== '') { $sql .= ' ORDER BY ' . $orderBySql; } } $sql = $this->_platform->modifyLimitQuery($sql, $this->_query->getMaxResults(), $this->_query->getFirstResult()); if (($lockMode = $this->_query->getHint(Query::HINT_LOCK_MODE)) !== false) { if ($lockMode == LockMode::PESSIMISTIC_READ) { $sql .= " " . $this->_platform->getReadLockSQL(); } else { if ($lockMode == LockMode::PESSIMISTIC_WRITE) { $sql .= " " . $this->_platform->getWriteLockSQL(); } else { if ($lockMode == LockMode::OPTIMISTIC) { foreach ($this->_selectedClasses as $class) { if (!$class->isVersioned) { throw \Doctrine\ORM\OptimisticLockException::lockFailed(); } } } } } } return $sql; }
/** * Perform UPDATE statement for an entity. This function has support for * optimistic locking if the entities ClassMetadata has versioning enabled. * * @param object $entity The entity object being updated * @param string $tableName The name of the table being updated * @param array $data The array of data to set * @param array $where The condition used to update * @return void */ protected function _doUpdate($entity, $tableName, $data, $where) { // Note: $tableName and column names in $data are already quoted for SQL. $set = array(); foreach ($data as $columnName => $value) { $set[] = $columnName . ' = ?'; } if ($isVersioned = $this->_class->isVersioned) { $versionField = $this->_class->versionField; $versionFieldType = $this->_class->getTypeOfField($versionField); $where[$versionField] = Type::getType($versionFieldType)->convertToDatabaseValue($this->_class->reflFields[$versionField]->getValue($entity), $this->_platform); $versionFieldColumnName = $this->_class->getQuotedColumnName($versionField, $this->_platform); if ($versionFieldType == 'integer') { $set[] = $versionFieldColumnName . ' = ' . $versionFieldColumnName . ' + 1'; } else { if ($versionFieldType == 'datetime') { $set[] = $versionFieldColumnName . ' = CURRENT_TIMESTAMP'; } } } $params = array_merge(array_values($data), array_values($where)); $sql = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $set) . ' WHERE ' . implode(' = ? AND ', array_keys($where)) . ' = ?'; $result = $this->_conn->executeUpdate($sql, $params); if ($isVersioned && !$result) { throw \Doctrine\ORM\OptimisticLockException::lockFailed(); } }
/** * Executes a merge operation on an entity. * * @param object $entity * @param array $visited * @return object The managed copy of the entity. * @throws OptimisticLockException If the entity uses optimistic locking through a version * attribute and the version check against the managed copy fails. * @throws InvalidArgumentException If the entity instance is NEW. */ private function _doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null) { $class = $this->_em->getClassMetadata(get_class($entity)); $id = $class->getIdentifierValues($entity); if (!$id) { throw new \InvalidArgumentException('New entity detected during merge.' . ' Persist the new entity before merging.'); } // MANAGED entities are ignored by the merge operation if ($this->getEntityState($entity, self::STATE_DETACHED) == self::STATE_MANAGED) { $managedCopy = $entity; } else { // Try to look the entity up in the identity map. $managedCopy = $this->tryGetById($id, $class->rootEntityName); if ($managedCopy) { // We have the entity in-memory already, just make sure its not removed. if ($this->getEntityState($managedCopy) == self::STATE_REMOVED) { throw new \InvalidArgumentException('Removed entity detected during merge.' . ' Can not merge with a removed entity.'); } } else { // We need to fetch the managed copy in order to merge. $managedCopy = $this->_em->find($class->name, $id); } if ($managedCopy === null) { throw new \InvalidArgumentException('New entity detected during merge.' . ' Persist the new entity before merging.'); } if ($class->isVersioned) { $managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy); $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); // Throw exception if versions dont match. if ($managedCopyVersion != $entityVersion) { throw OptimisticLockException::lockFailed(); } } // Merge state of $entity into existing (managed) entity foreach ($class->reflFields as $name => $prop) { if (!isset($class->associationMappings[$name])) { $prop->setValue($managedCopy, $prop->getValue($entity)); } else { $assoc2 = $class->associationMappings[$name]; if ($assoc2->isOneToOne()) { if (!$assoc2->isCascadeMerge) { $other = $class->reflFields[$name]->getValue($entity); //TODO: Just $prop->getValue($entity)? if ($other !== null) { $targetClass = $this->_em->getClassMetadata($assoc2->targetEntityName); $id = $targetClass->getIdentifierValues($other); $proxy = $this->_em->getProxyFactory()->getProxy($assoc2->targetEntityName, $id); $prop->setValue($managedCopy, $proxy); $this->registerManaged($proxy, $id, array()); } } } else { $coll = new PersistentCollection($this->_em, $this->_em->getClassMetadata($assoc2->targetEntityName), new ArrayCollection()); $coll->setOwner($managedCopy, $assoc2); $coll->setInitialized($assoc2->isCascadeMerge); $prop->setValue($managedCopy, $coll); } } if ($class->isChangeTrackingNotify()) { //TODO: put changed fields in changeset...? } } if ($class->isChangeTrackingDeferredExplicit()) { //TODO: Mark $managedCopy for dirty check...? ($this->_scheduledForDirtyCheck) } } if ($prevManagedCopy !== null) { $assocField = $assoc->sourceFieldName; $prevClass = $this->_em->getClassMetadata(get_class($prevManagedCopy)); if ($assoc->isOneToOne()) { $prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy); //TODO: What about back-reference if bidirectional? } else { $prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->unwrap()->add($managedCopy); if ($assoc->isOneToMany()) { $class->reflFields[$assoc->mappedBy]->setValue($managedCopy, $prevManagedCopy); } } } $this->_cascadeMerge($entity, $managedCopy, $visited); return $managedCopy; }
/** * Finds an entity by its primary key / identifier. * * @param $id The identifier. * @param int $lockMode * @param int $lockVersion * @return object The entity. */ public function find($id, $lockMode = LockMode::NONE, $lockVersion = null) { if (!is_array($id)) { $id = array($this->_class->identifier[0] => $id); } $sortedId = array(); foreach ($this->_class->identifier as $identifier) { if (!isset($id[$identifier])) { throw ORMException::missingIdentifierField($this->_class->name, $identifier); } $sortedId[$identifier] = $id[$identifier]; } // Check identity map first if (($entity = $this->_em->getUnitOfWork()->tryGetById($sortedId, $this->_class->rootEntityName)) !== false) { if (!$entity instanceof $this->_class->name) { return null; } if ($lockMode !== LockMode::NONE) { $this->_em->lock($entity, $lockMode, $lockVersion); } return $entity; // Hit! } $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); switch ($lockMode) { case LockMode::NONE: return $persister->load($sortedId); case LockMode::OPTIMISTIC: if (!$this->_class->isVersioned) { throw OptimisticLockException::notVersioned($this->_entityName); } $entity = $persister->load($sortedId); $this->_em->getUnitOfWork()->lock($entity, $lockMode, $lockVersion); return $entity; default: if (!$this->_em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } return $persister->load($sortedId, null, null, array(), $lockMode); } }
/** * Acquires an optimistic lock within a pessimistic lock transaction. For * use in fail-fast scenarios; guaranteed to throw an exception on * concurrent modification attempts. The one to first acquire the write lock * will update the version field, leading subsequent acquisitions of the * optimistic lock to fail. * * FIXME: Only works on entities implementing VersionLockable and does not * work in conjunction with the Doctrine @Version column. * * @param int $id * @param mixed $lockVersion * @param callback($entity, Doctrine\ORM\EntityManager, Repository) $callback * @return mixed callback return type * @throws OptimisticLockException */ public function useWithPessimisticVersionLock($id, $lockVersion, $callback) { return $this->useWithPessimisticWriteLock($id, function (VersionLockable $entity, EntityManager $em, $self) use($lockVersion, $callback) { if ($entity->getVersion() !== $lockVersion) { // FIXME: This isn't the appropriate exception type. throw OptimisticLockException::lockFailedVersionMissmatch($entity, $lockVersion, $entity->getVersion()); } $entity->incrementVersion(); return $callback($entity, $em, $self); }); }