mirror of
https://github.com/doctrine/orm.git
synced 2026-03-25 15:32:18 +01:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78fa740b94 | ||
|
|
715fdd7559 | ||
|
|
884ef42075 | ||
|
|
5d3a626c2b | ||
|
|
587dda90d3 | ||
|
|
21ae0d2a45 | ||
|
|
f4dc533127 | ||
|
|
c9d1b3a428 | ||
|
|
9170e1b810 | ||
|
|
f1d2a79a87 | ||
|
|
dd2f3967cd | ||
|
|
4100a6a7a3 | ||
|
|
bfb590459e | ||
|
|
191520439b | ||
|
|
31709b4c1a | ||
|
|
af6de10c5b | ||
|
|
f4a78e13ee | ||
|
|
bd37b83f4f | ||
|
|
19969e2d4b | ||
|
|
573a7f81e1 | ||
|
|
2460b3f115 | ||
|
|
5f2fa7c08a | ||
|
|
a04df0622d | ||
|
|
b7e80decf3 | ||
|
|
56d35f7932 | ||
|
|
2116518096 | ||
|
|
647bd2b2f2 | ||
|
|
dadc85a650 | ||
|
|
835943bd8d | ||
|
|
39ff164c7e | ||
|
|
5cdbc8ea6d | ||
|
|
34ccde978a | ||
|
|
5675e8cc8c |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "doctrine/orm",
|
||||
"type": "library","version":"2.2.0",
|
||||
"type": "library","version":"2.2.1",
|
||||
"description": "Object-Relational-Mapper for PHP",
|
||||
"keywords": ["orm", "database"],
|
||||
"homepage": "http://www.doctrine-project.org",
|
||||
|
||||
@@ -350,6 +350,7 @@
|
||||
<xs:attribute name="index-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
@@ -208,6 +208,7 @@ abstract class AbstractQuery
|
||||
{
|
||||
$key = trim($key, ':');
|
||||
|
||||
$value = $this->processParameterValue($value);
|
||||
if ($type === null) {
|
||||
$type = Query\ParameterTypeInferer::inferType($value);
|
||||
}
|
||||
@@ -218,6 +219,53 @@ abstract class AbstractQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an individual parameter value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
private function processParameterValue($value)
|
||||
{
|
||||
switch (true) {
|
||||
case is_array($value):
|
||||
for ($i = 0, $l = count($value); $i < $l; $i++) {
|
||||
$paramValue = $this->processParameterValue($value[$i]);
|
||||
$value[$i] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue;
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
||||
case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value)):
|
||||
return $this->convertObjectParameterToScalarValue($value);
|
||||
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
protected function convertObjectParameterToScalarValue($value)
|
||||
{
|
||||
$class = $this->_em->getClassMetadata(get_class($value));
|
||||
|
||||
if ($class->isIdentifierComposite) {
|
||||
throw new \InvalidArgumentException("Binding an entity with a composite primary key to a query is not supported. You should split the parameter into the explicit fields and bind them seperately.");
|
||||
}
|
||||
|
||||
if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) {
|
||||
$values = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
|
||||
} else {
|
||||
$values = $class->getIdentifierValues($value);
|
||||
}
|
||||
|
||||
$value = $values[$class->getSingleIdentifierFieldName()];
|
||||
if (!$value) {
|
||||
throw new \InvalidArgumentException("Binding entities to query parameters only allowed for entities that have an identifier.");
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a collection of query parameters.
|
||||
*
|
||||
|
||||
@@ -233,7 +233,7 @@ abstract class AbstractHydrator
|
||||
|
||||
if (isset($cache[$key]['isScalar'])) {
|
||||
$value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform);
|
||||
|
||||
|
||||
$rowData['scalars'][$cache[$key]['fieldName']] = $value;
|
||||
|
||||
continue;
|
||||
|
||||
@@ -286,4 +286,4 @@ class ArrayHydrator extends AbstractHydrator
|
||||
|
||||
return $this->_ce[$className];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,20 +234,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
|
||||
$this->_hints['fetchAlias'] = $dqlAlias;
|
||||
|
||||
$entity = $this->_uow->createEntity($className, $data, $this->_hints);
|
||||
|
||||
//TODO: These should be invoked later, after hydration, because associations may not yet be loaded here.
|
||||
if (isset($this->_ce[$className]->lifecycleCallbacks[Events::postLoad])) {
|
||||
$this->_ce[$className]->invokeLifecycleCallbacks(Events::postLoad, $entity);
|
||||
}
|
||||
|
||||
$evm = $this->_em->getEventManager();
|
||||
|
||||
if ($evm->hasListeners(Events::postLoad)) {
|
||||
$evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
|
||||
}
|
||||
|
||||
return $entity;
|
||||
return $this->_uow->createEntity($className, $data, $this->_hints);
|
||||
}
|
||||
|
||||
private function _getEntityFromIdentityMap($className, array $data)
|
||||
|
||||
@@ -130,17 +130,6 @@ class SimpleObjectHydrator extends AbstractHydrator
|
||||
$uow = $this->_em->getUnitOfWork();
|
||||
$entity = $uow->createEntity($entityName, $data, $this->_hints);
|
||||
|
||||
//TODO: These should be invoked later, after hydration, because associations may not yet be loaded here.
|
||||
if (isset($this->class->lifecycleCallbacks[Events::postLoad])) {
|
||||
$this->class->invokeLifecycleCallbacks(Events::postLoad, $entity);
|
||||
}
|
||||
|
||||
$evm = $this->_em->getEventManager();
|
||||
|
||||
if ($evm->hasListeners(Events::postLoad)) {
|
||||
$evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
|
||||
}
|
||||
|
||||
$result[] = $entity;
|
||||
}
|
||||
|
||||
@@ -191,4 +180,4 @@ class SimpleObjectHydrator extends AbstractHydrator
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1105,7 +1105,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
$mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
|
||||
}
|
||||
|
||||
if ( ($mapping['type'] & (self::MANY_TO_ONE|self::MANY_TO_MANY)) > 0 &&
|
||||
if ( ($mapping['type'] & self::MANY_TO_ONE) > 0 &&
|
||||
isset($mapping['orphanRemoval']) &&
|
||||
$mapping['orphanRemoval'] == true) {
|
||||
|
||||
@@ -1207,7 +1207,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
|
||||
$uniqueContraintColumns = array();
|
||||
foreach ($mapping['joinColumns'] as $key => &$joinColumn) {
|
||||
if ($mapping['type'] === self::ONE_TO_ONE) {
|
||||
if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) {
|
||||
if (count($mapping['joinColumns']) == 1) {
|
||||
$joinColumn['unique'] = true;
|
||||
} else {
|
||||
@@ -1336,6 +1336,8 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
}
|
||||
}
|
||||
|
||||
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
|
||||
|
||||
if (isset($mapping['orderBy'])) {
|
||||
if ( ! is_array($mapping['orderBy'])) {
|
||||
throw new \InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy']));
|
||||
|
||||
@@ -142,8 +142,7 @@ class AnnotationDriver implements Driver
|
||||
|
||||
$classAnnotations = $this->_reader->getClassAnnotations($class);
|
||||
|
||||
// Compatibility with Doctrine Common 3.x
|
||||
if ($classAnnotations && is_int(key($classAnnotations))) {
|
||||
if ($classAnnotations && is_numeric(key($classAnnotations))) {
|
||||
foreach ($classAnnotations as $annot) {
|
||||
$classAnnotations[get_class($annot)] = $annot;
|
||||
}
|
||||
@@ -176,17 +175,25 @@ class AnnotationDriver implements Driver
|
||||
|
||||
if ($tableAnnot->indexes !== null) {
|
||||
foreach ($tableAnnot->indexes as $indexAnnot) {
|
||||
$primaryTable['indexes'][$indexAnnot->name] = array(
|
||||
'columns' => $indexAnnot->columns
|
||||
);
|
||||
$index = array('columns' => $indexAnnot->columns);
|
||||
|
||||
if ( ! empty($indexAnnot->name)) {
|
||||
$primaryTable['indexes'][$indexAnnot->name] = $index;
|
||||
} else {
|
||||
$primaryTable['indexes'][] = $index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($tableAnnot->uniqueConstraints !== null) {
|
||||
foreach ($tableAnnot->uniqueConstraints as $uniqueConstraint) {
|
||||
$primaryTable['uniqueConstraints'][$uniqueConstraint->name] = array(
|
||||
'columns' => $uniqueConstraint->columns
|
||||
);
|
||||
foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
|
||||
$uniqueConstraint = array('columns' => $uniqueConstraintAnnot->columns);
|
||||
|
||||
if ( ! empty($uniqueConstraintAnnot->name)) {
|
||||
$primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint;
|
||||
} else {
|
||||
$primaryTable['uniqueConstraints'][] = $uniqueConstraint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,6 +414,7 @@ class AnnotationDriver implements Driver
|
||||
$mapping['inversedBy'] = $manyToManyAnnot->inversedBy;
|
||||
$mapping['cascade'] = $manyToManyAnnot->cascade;
|
||||
$mapping['indexBy'] = $manyToManyAnnot->indexBy;
|
||||
$mapping['orphanRemoval'] = $manyToManyAnnot->orphanRemoval;
|
||||
$mapping['fetch'] = $this->getFetchMode($className, $manyToManyAnnot->fetch);
|
||||
|
||||
if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) {
|
||||
@@ -424,8 +432,7 @@ class AnnotationDriver implements Driver
|
||||
if ($method->isPublic() && $method->getDeclaringClass()->getName() == $class->name) {
|
||||
$annotations = $this->_reader->getMethodAnnotations($method);
|
||||
|
||||
// Compatibility with Doctrine Common 3.x
|
||||
if ($annotations && is_int(key($annotations))) {
|
||||
if ($annotations && is_numeric(key($annotations))) {
|
||||
foreach ($annotations as $annot) {
|
||||
$annotations[get_class($annot)] = $annot;
|
||||
}
|
||||
@@ -480,8 +487,7 @@ class AnnotationDriver implements Driver
|
||||
{
|
||||
$classAnnotations = $this->_reader->getClassAnnotations(new \ReflectionClass($className));
|
||||
|
||||
// Compatibility with Doctrine Common 3.x
|
||||
if ($classAnnotations && is_int(key($classAnnotations))) {
|
||||
if ($classAnnotations && is_numeric(key($classAnnotations))) {
|
||||
foreach ($classAnnotations as $annot) {
|
||||
if ($annot instanceof \Doctrine\ORM\Mapping\Entity) {
|
||||
return false;
|
||||
|
||||
@@ -397,6 +397,10 @@ class XmlDriver extends AbstractFileDriver
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$manyToManyElement['fetch']);
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['orphan-removal'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$manyToManyElement['orphan-removal'];
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['mapped-by'])) {
|
||||
$mapping['mappedBy'] = (string)$manyToManyElement['mapped-by'];
|
||||
} else if (isset($manyToManyElement->{'join-table'})) {
|
||||
|
||||
@@ -446,6 +446,10 @@ class YamlDriver extends AbstractFileDriver
|
||||
$mapping['indexBy'] = $manyToManyElement['indexBy'];
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['orphanRemoval'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$manyToManyElement['orphanRemoval'];
|
||||
}
|
||||
|
||||
$metadata->mapManyToMany($mapping);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ final class ManyToMany implements Annotation
|
||||
public $cascade;
|
||||
/** @var string */
|
||||
public $fetch = 'LAZY';
|
||||
/** @var boolean */
|
||||
public $orphanRemoval = false;
|
||||
/** @var string */
|
||||
public $indexBy;
|
||||
}
|
||||
|
||||
@@ -305,6 +305,7 @@ final class PersistentCollection implements Collection
|
||||
if ($this->association !== null &&
|
||||
$this->association['isOwningSide'] &&
|
||||
$this->association['type'] === ClassMetadata::MANY_TO_MANY &&
|
||||
$this->owner &&
|
||||
$this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
|
||||
$this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner);
|
||||
}
|
||||
@@ -387,7 +388,7 @@ final class PersistentCollection implements Collection
|
||||
$this->changed();
|
||||
|
||||
if ($this->association !== null &&
|
||||
$this->association['type'] == ClassMetadata::ONE_TO_MANY &&
|
||||
$this->association['type'] & ClassMetadata::TO_MANY &&
|
||||
$this->association['orphanRemoval']) {
|
||||
$this->em->getUnitOfWork()->scheduleOrphanRemoval($removed);
|
||||
}
|
||||
@@ -425,7 +426,7 @@ final class PersistentCollection implements Collection
|
||||
$this->changed();
|
||||
|
||||
if ($this->association !== null &&
|
||||
$this->association['type'] === ClassMetadata::ONE_TO_MANY &&
|
||||
$this->association['type'] & ClassMetadata::TO_MANY &&
|
||||
$this->association['orphanRemoval']) {
|
||||
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
|
||||
}
|
||||
@@ -630,7 +631,7 @@ final class PersistentCollection implements Collection
|
||||
|
||||
$uow = $this->em->getUnitOfWork();
|
||||
|
||||
if ($this->association['type'] === ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) {
|
||||
if ($this->association['type'] & ClassMetadata::TO_MANY && $this->association['orphanRemoval']) {
|
||||
// we need to initialize here, as orphan removal acts like implicit cascadeRemove,
|
||||
// hence for event listeners we need the objects in memory.
|
||||
$this->initialize();
|
||||
@@ -759,4 +760,27 @@ final class PersistentCollection implements Collection
|
||||
|
||||
return $this->coll->slice($offset, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup internal state of cloned persistent collection.
|
||||
*
|
||||
* The following problems have to be prevented:
|
||||
* 1. Added entities are added to old PC
|
||||
* 2. New collection is not dirty, if reused on other entity nothing
|
||||
* changes.
|
||||
* 3. Snapshot leads to invalid diffs being generated.
|
||||
* 4. Lazy loading grabs entities from old owner object.
|
||||
* 5. New collection is connected to old owner and leads to duplicate keys.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->initialize();
|
||||
$this->owner = null;
|
||||
|
||||
if (is_object($this->coll)) {
|
||||
$this->coll = clone $this->coll;
|
||||
}
|
||||
$this->snapshot = array();
|
||||
$this->changed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,4 +204,4 @@ abstract class AbstractCollectionPersister
|
||||
* @param mixed $element
|
||||
*/
|
||||
abstract protected function _getInsertRowSQLParameters(PersistentCollection $coll, $element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -698,16 +698,6 @@ class BasicEntityPersister
|
||||
|
||||
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
|
||||
$hydrator->hydrateAll($stmt, $this->_rsm, array(Query::HINT_REFRESH => true));
|
||||
|
||||
if (isset($this->_class->lifecycleCallbacks[Events::postLoad])) {
|
||||
$this->_class->invokeLifecycleCallbacks(Events::postLoad, $entity);
|
||||
}
|
||||
|
||||
$evm = $this->_em->getEventManager();
|
||||
|
||||
if ($evm->hasListeners(Events::postLoad)) {
|
||||
$evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -106,11 +106,16 @@ class ProxyFactory
|
||||
* Generate the Proxy file name
|
||||
*
|
||||
* @param string $className
|
||||
* @param string $baseDir Optional base directory for proxy file name generation.
|
||||
* If not specified, the directory configured on the Configuration of the
|
||||
* EntityManager will be used by this factory.
|
||||
* @return string
|
||||
*/
|
||||
private function getProxyFileName($className)
|
||||
private function getProxyFileName($className, $baseDir = null)
|
||||
{
|
||||
return $this->_proxyDir . DIRECTORY_SEPARATOR . '__CG__' . str_replace('\\', '', $className) . '.php';
|
||||
$proxyDir = $baseDir ?: $this->_proxyDir;
|
||||
|
||||
return $proxyDir . DIRECTORY_SEPARATOR . '__CG__' . str_replace('\\', '', $className) . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,14 +129,16 @@ class ProxyFactory
|
||||
public function generateProxyClasses(array $classes, $toDir = null)
|
||||
{
|
||||
$proxyDir = $toDir ?: $this->_proxyDir;
|
||||
$proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
$proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR);
|
||||
|
||||
foreach ($classes as $class) {
|
||||
/* @var $class ClassMetadata */
|
||||
if ($class->isMappedSuperclass) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$proxyFileName = $this->getProxyFileName($class->name);
|
||||
$proxyFileName = $this->getProxyFileName($class->name, $proxyDir);
|
||||
|
||||
$this->_generateProxyClass($class, $proxyFileName, self::$_proxyClassTemplate);
|
||||
}
|
||||
}
|
||||
@@ -275,7 +282,7 @@ class ProxyFactory
|
||||
);
|
||||
|
||||
if ($cheapCheck) {
|
||||
$code = file($class->reflClass->getFileName());
|
||||
$code = file($method->getDeclaringClass()->getFileName());
|
||||
$code = trim(implode(" ", array_slice($code, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1)));
|
||||
|
||||
$pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier);
|
||||
|
||||
@@ -282,7 +282,8 @@ final class Query extends AbstractQuery
|
||||
}
|
||||
|
||||
$sqlPositions = $paramMappings[$key];
|
||||
$value = array_values($this->processParameterValue($value));
|
||||
// optimized multi value sql positions away for now, they are not allowed in DQL anyways.
|
||||
$value = array($value);
|
||||
$countValue = count($value);
|
||||
|
||||
for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) {
|
||||
@@ -305,39 +306,6 @@ final class Query extends AbstractQuery
|
||||
return array($sqlParams, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an individual parameter value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
private function processParameterValue($value)
|
||||
{
|
||||
switch (true) {
|
||||
case is_array($value):
|
||||
for ($i = 0, $l = count($value); $i < $l; $i++) {
|
||||
$paramValue = $this->processParameterValue($value[$i]);
|
||||
|
||||
// TODO: What about Entities that have composite primary key?
|
||||
$value[$i] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue;
|
||||
}
|
||||
|
||||
return array($value);
|
||||
|
||||
case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value)):
|
||||
if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) {
|
||||
return array_values($this->_em->getUnitOfWork()->getEntityIdentifier($value));
|
||||
}
|
||||
|
||||
$class = $this->_em->getClassMetadata(get_class($value));
|
||||
|
||||
return array_values($class->getIdentifierValues($value));
|
||||
|
||||
default:
|
||||
return array($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a cache driver to be used for caching queries.
|
||||
*
|
||||
|
||||
@@ -636,11 +636,17 @@ class SqlWalker implements TreeWalker
|
||||
}
|
||||
|
||||
// Add foreign key columns to SQL, if necessary
|
||||
if ( ! $addMetaColumns) continue;
|
||||
if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add foreign key columns of class and also parent classes
|
||||
foreach ($class->associationMappings as $assoc) {
|
||||
if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue;
|
||||
if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
|
||||
continue;
|
||||
} else if ( !$addMetaColumns && !isset($assoc['id'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$owningClass = (isset($assoc['inherited'])) ? $this->_em->getClassMetadata($assoc['inherited']) : $class;
|
||||
$sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
|
||||
@@ -654,6 +660,11 @@ class SqlWalker implements TreeWalker
|
||||
}
|
||||
}
|
||||
|
||||
// Add foreign key columns to SQL, if necessary
|
||||
if ( ! $addMetaColumns) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add foreign key columns of subclasses
|
||||
foreach ($class->subClasses as $subClassName) {
|
||||
$subClass = $this->_em->getClassMetadata($subClassName);
|
||||
|
||||
@@ -128,10 +128,12 @@ public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
|
||||
* <description>
|
||||
*
|
||||
* @param <variableType>$<variableName>
|
||||
* @return <entity>
|
||||
*/
|
||||
public function <methodName>(<methodTypeHint>$<variableName>)
|
||||
{
|
||||
<spaces>$this-><fieldName>[] = $<variableName>;
|
||||
<spaces>return $this;
|
||||
}';
|
||||
|
||||
private static $_lifecycleCallbackMethodTemplate =
|
||||
@@ -681,6 +683,9 @@ public function <methodName>()
|
||||
|
||||
private function _isAssociationIsNullable($associationMapping)
|
||||
{
|
||||
if (isset($associationMapping['id']) && $associationMapping['id']) {
|
||||
return false;
|
||||
}
|
||||
if (isset($associationMapping['joinColumns'])) {
|
||||
$joinColumns = $associationMapping['joinColumns'];
|
||||
} else {
|
||||
|
||||
@@ -106,6 +106,11 @@ class SchemaValidator
|
||||
|
||||
$targetMetadata = $cmf->getMetadataFor($assoc['targetEntity']);
|
||||
|
||||
if (isset($assoc['id']) && $targetMetadata->containsForeignIdentifier) {
|
||||
$ce[] = "Cannot map association '" . $class->name. "#". $fieldName ." as identifier, because " .
|
||||
"the target entity '". $targetMetadata->name . "' also maps an association as identifier.";
|
||||
}
|
||||
|
||||
/* @var $assoc AssociationMapping */
|
||||
if ($assoc['mappedBy']) {
|
||||
if ($targetMetadata->hasField($assoc['mappedBy'])) {
|
||||
|
||||
@@ -584,6 +584,23 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
$assoc = $class->associationMappings[$propName];
|
||||
|
||||
// Persistent collection was exchanged with the "originally"
|
||||
// created one. This can only mean it was cloned and replaced
|
||||
// on another entity.
|
||||
if ($actualValue instanceof PersistentCollection) {
|
||||
$owner = $actualValue->getOwner();
|
||||
if ($owner === null) { // cloned
|
||||
$actualValue->setOwner($entity, $assoc);
|
||||
} else if ($owner !== $entity) { // no clone, we have to fix
|
||||
if (!$actualValue->isInitialized()) {
|
||||
$actualValue->initialize(); // we have to do this otherwise the cols share state
|
||||
}
|
||||
$newValue = clone $actualValue;
|
||||
$newValue->setOwner($entity, $assoc);
|
||||
$class->reflFields[$propName]->setValue($entity, $newValue);
|
||||
}
|
||||
}
|
||||
|
||||
if ($orgValue instanceof PersistentCollection) {
|
||||
// A PersistentCollection was de-referenced, so delete it.
|
||||
$coid = spl_object_hash($orgValue);
|
||||
@@ -620,6 +637,15 @@ class UnitOfWork implements PropertyChangedListener
|
||||
foreach ($class->associationMappings as $field => $assoc) {
|
||||
if (($val = $class->reflFields[$field]->getValue($entity)) !== null) {
|
||||
$this->computeAssociationChanges($assoc, $val);
|
||||
if (!isset($this->entityChangeSets[$oid]) &&
|
||||
$assoc['isOwningSide'] &&
|
||||
$assoc['type'] == ClassMetadata::MANY_TO_MANY &&
|
||||
$val instanceof PersistentCollection &&
|
||||
$val->isDirty()) {
|
||||
$this->entityChangeSets[$oid] = array();
|
||||
$this->originalEntityData[$oid] = $actualData;
|
||||
$this->entityUpdates[$oid] = $entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1376,6 +1402,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
if (isset($this->identityMap[$className][$idHash])) {
|
||||
unset($this->identityMap[$className][$idHash]);
|
||||
unset($this->readOnlyObjects[$oid]);
|
||||
|
||||
//$this->entityStates[$oid] = self::STATE_DETACHED;
|
||||
|
||||
@@ -2189,6 +2216,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$this->collectionDeletions =
|
||||
$this->collectionUpdates =
|
||||
$this->extraUpdates =
|
||||
$this->readOnlyObjects =
|
||||
$this->orphanRemovals = array();
|
||||
|
||||
if ($this->commitOrderCalculator !== null) {
|
||||
@@ -2498,6 +2526,17 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
}
|
||||
|
||||
if ($overrideLocalValues) {
|
||||
if (isset($class->lifecycleCallbacks[Events::postLoad])) {
|
||||
$class->invokeLifecycleCallbacks(Events::postLoad, $entity);
|
||||
}
|
||||
|
||||
|
||||
if ($this->evm->hasListeners(Events::postLoad)) {
|
||||
$this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->em));
|
||||
}
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class Version
|
||||
/**
|
||||
* Current Doctrine Version
|
||||
*/
|
||||
const VERSION = '2.2.0';
|
||||
const VERSION = '2.2.1';
|
||||
|
||||
/**
|
||||
* Compares a Doctrine version with the current one.
|
||||
|
||||
2
lib/vendor/doctrine-common
vendored
2
lib/vendor/doctrine-common
vendored
Submodule lib/vendor/doctrine-common updated: 84838bb8c9...6c7f9cfd46
@@ -4,6 +4,7 @@ namespace Doctrine\Tests\ORM\Functional;
|
||||
use Doctrine\Tests\Models\Navigation\NavCountry;
|
||||
use Doctrine\Tests\Models\Navigation\NavPointOfInterest;
|
||||
use Doctrine\Tests\Models\Navigation\NavTour;
|
||||
use Doctrine\Tests\Models\Navigation\NavPhotos;
|
||||
|
||||
require_once __DIR__ . '/../../TestInit.php';
|
||||
|
||||
@@ -51,6 +52,25 @@ class CompositePrimaryKeyTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
$this->assertEquals('Brandenburger Tor', $poi->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-1651
|
||||
*/
|
||||
public function testSetParameterCompositeKeyObject()
|
||||
{
|
||||
$this->putGermanysBrandenburderTor();
|
||||
|
||||
$poi = $this->_em->find('Doctrine\Tests\Models\Navigation\NavPointOfInterest', array('lat' => 100, 'long' => 200));
|
||||
$photo = new NavPhotos($poi, "asdf");
|
||||
$this->_em->persist($photo);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$dql = 'SELECT t FROM Doctrine\Tests\Models\Navigation\NavPhotos t WHERE t.poi = ?1';
|
||||
|
||||
$this->setExpectedException('Doctrine\ORM\Query\QueryException', 'A single-valued association path expression to an entity with a composite primary key is not supported.');
|
||||
$sql = $this->_em->createQuery($dql)->getSQL();
|
||||
}
|
||||
|
||||
public function testManyToManyCompositeRelation()
|
||||
{
|
||||
$this->putGermanysBrandenburderTor();
|
||||
@@ -98,4 +118,4 @@ class CompositePrimaryKeyTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
$this->setExpectedException('Doctrine\ORM\ORMException', 'The identifier long is missing for a query of Doctrine\Tests\Models\Navigation\NavPointOfInterest');
|
||||
$poi = $this->_em->find('Doctrine\Tests\Models\Navigation\NavPointOfInterest', array('key1' => 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
76
tests/Doctrine/Tests/ORM/Functional/ManyToManyEventTest.php
Normal file
76
tests/Doctrine/Tests/ORM/Functional/ManyToManyEventTest.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\Models\CMS\CmsGroup;
|
||||
use Doctrine\ORM\Events;
|
||||
require_once __DIR__ . '/../../TestInit.php';
|
||||
|
||||
/**
|
||||
* ManyToManyEventTest
|
||||
*
|
||||
* @author Francisco Facioni <fran6co@gmail.com>
|
||||
*/
|
||||
class ManyToManyEventTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
{
|
||||
/**
|
||||
* @var PostUpdateListener
|
||||
*/
|
||||
private $listener;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->useModelSet('cms');
|
||||
parent::setUp();
|
||||
$this->listener = new PostUpdateListener();
|
||||
$evm = $this->_em->getEventManager();
|
||||
$evm->addEventListener(Events::postUpdate, $this->listener);
|
||||
}
|
||||
|
||||
public function testListenerShouldBeNotifiedOnlyWhenUpdating()
|
||||
{
|
||||
$user = $this->createNewValidUser();
|
||||
$this->_em->persist($user);
|
||||
$this->_em->flush();
|
||||
$this->assertFalse($this->listener->wasNotified);
|
||||
|
||||
$group = new CmsGroup();
|
||||
$group->name = "admins";
|
||||
$user->addGroup($group);
|
||||
$this->_em->persist($user);
|
||||
$this->_em->flush();
|
||||
|
||||
$this->assertTrue($this->listener->wasNotified);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CmsUser
|
||||
*/
|
||||
private function createNewValidUser()
|
||||
{
|
||||
$user = new CmsUser();
|
||||
$user->username = 'fran6co';
|
||||
$user->name = 'Francisco Facioni';
|
||||
$group = new CmsGroup();
|
||||
$group->name = "users";
|
||||
$user->addGroup($group);
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
class PostUpdateListener
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $wasNotified = false;
|
||||
|
||||
/**
|
||||
* @param $args
|
||||
*/
|
||||
public function postUpdate($args)
|
||||
{
|
||||
$this->wasNotified = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -599,4 +599,24 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
$this->assertEquals(3, count($users));
|
||||
$this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $users[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-1651
|
||||
*/
|
||||
public function testSetParameterBindingSingleIdentifierObjectConverted()
|
||||
{
|
||||
$userC = new CmsUser;
|
||||
$userC->name = 'Jonathan';
|
||||
$userC->username = 'jwage';
|
||||
$userC->status = 'developer';
|
||||
$this->_em->persist($userC);
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$q = $this->_em->createQuery("SELECT DISTINCT u from Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1");
|
||||
$q->setParameter(1, $userC);
|
||||
|
||||
$this->assertEquals($userC->id, $q->getParameter(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,12 @@ class ReadOnlyTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->_schemaTool->createSchema(array(
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\ReadOnlyEntity'),
|
||||
));
|
||||
try {
|
||||
$this->_schemaTool->createSchema(array(
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\ReadOnlyEntity'),
|
||||
));
|
||||
} catch(\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
public function testReadOnlyEntityNeverChangeTracked()
|
||||
@@ -36,6 +39,36 @@ class ReadOnlyTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
$this->assertEquals("Test1", $dbReadOnly->name);
|
||||
$this->assertEquals(1234, $dbReadOnly->numericValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-1659
|
||||
*/
|
||||
public function testClearReadOnly()
|
||||
{
|
||||
$readOnly = new ReadOnlyEntity("Test1", 1234);
|
||||
$this->_em->persist($readOnly);
|
||||
$this->_em->flush();
|
||||
$this->_em->getUnitOfWork()->markReadOnly($readOnly);
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
$this->assertFalse($this->_em->getUnitOfWork()->isReadOnly($readOnly));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-1659
|
||||
*/
|
||||
public function testClearEntitiesReadOnly()
|
||||
{
|
||||
$readOnly = new ReadOnlyEntity("Test1", 1234);
|
||||
$this->_em->persist($readOnly);
|
||||
$this->_em->flush();
|
||||
$this->_em->getUnitOfWork()->markReadOnly($readOnly);
|
||||
|
||||
$this->_em->clear(get_class($readOnly));
|
||||
|
||||
$this->assertFalse($this->_em->getUnitOfWork()->isReadOnly($readOnly));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,4 +91,4 @@ class ReadOnlyEntity
|
||||
$this->name = $name;
|
||||
$this->numericValue = $number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\ORM\Proxy\ProxyClassGenerator;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceProduct;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceShipping;
|
||||
use Doctrine\Tests\Models\Company\CompanyAuction;
|
||||
|
||||
require_once __DIR__ . '/../../TestInit.php';
|
||||
|
||||
@@ -39,6 +40,18 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
return $product->getId();
|
||||
}
|
||||
|
||||
public function createAuction()
|
||||
{
|
||||
$event = new CompanyAuction();
|
||||
$event->setData('Doctrine Cookbook');
|
||||
$this->_em->persist($event);
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
return $event->getId();
|
||||
}
|
||||
|
||||
public function testLazyLoadsFieldValuesFromDatabase()
|
||||
{
|
||||
$id = $this->createProduct();
|
||||
@@ -161,6 +174,21 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
$this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-1625
|
||||
*/
|
||||
public function testDoNotInitializeProxyOnGettingTheIdentifier_DDC_1625()
|
||||
{
|
||||
$id = $this->createAuction();
|
||||
|
||||
/* @var $entity Doctrine\Tests\Models\Company\CompanyAuction */
|
||||
$entity = $this->_em->getReference('Doctrine\Tests\Models\Company\CompanyAuction' , $id);
|
||||
|
||||
$this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy.");
|
||||
$this->assertEquals($id, $entity->getId());
|
||||
$this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy when extending.");
|
||||
}
|
||||
|
||||
public function testDoNotInitializeProxyOnGettingTheIdentifierAndReturnTheRightType()
|
||||
{
|
||||
$product = new ECommerceProduct();
|
||||
|
||||
@@ -433,4 +433,32 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
$this->assertEquals($this->article1->id(), $refRep->source()->id());
|
||||
$this->assertEquals($this->article2->id(), $refRep->target()->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-1652
|
||||
*/
|
||||
public function testArrayHydrationWithCompositeKey()
|
||||
{
|
||||
$dql = "SELECT r,s,t FROM Doctrine\Tests\Models\DDC117\DDC117Reference r INNER JOIN r.source s INNER JOIN r.target t";
|
||||
$before = count($this->_em->createQuery($dql)->getResult());
|
||||
|
||||
$this->article1 = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Article", $this->article1->id());
|
||||
$this->article2 = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Article", $this->article2->id());
|
||||
|
||||
$this->reference = new DDC117Reference($this->article2, $this->article1, "Test-Description");
|
||||
$this->_em->persist($this->reference);
|
||||
|
||||
$this->reference = new DDC117Reference($this->article1, $this->article1, "Test-Description");
|
||||
$this->_em->persist($this->reference);
|
||||
|
||||
$this->reference = new DDC117Reference($this->article2, $this->article2, "Test-Description");
|
||||
$this->_em->persist($this->reference);
|
||||
|
||||
$this->_em->flush();
|
||||
|
||||
$dql = "SELECT r,s,t FROM Doctrine\Tests\Models\DDC117\DDC117Reference r INNER JOIN r.source s INNER JOIN r.target t";
|
||||
$data = $this->_em->createQuery($dql)->getArrayResult();
|
||||
|
||||
$this->assertEquals($before + 3, count($data));
|
||||
}
|
||||
}
|
||||
|
||||
121
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1643Test.php
Normal file
121
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1643Test.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\Models\CMS\CmsGroup;
|
||||
|
||||
/**
|
||||
* @group DDC-1643
|
||||
*/
|
||||
class DDC1643Test extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
{
|
||||
private $user1;
|
||||
private $user2;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->useModelSet('cms');
|
||||
parent::setUp();
|
||||
|
||||
$user1 = new CmsUser();
|
||||
$user1->username = "beberlei";
|
||||
$user1->name = "Benjamin";
|
||||
$user1->status = "active";
|
||||
$group1 = new CmsGroup();
|
||||
$group1->name = "test";
|
||||
$group2 = new CmsGroup();
|
||||
$group2->name = "test";
|
||||
$user1->addGroup($group1);
|
||||
$user1->addGroup($group2);
|
||||
$user2 = new CmsUser();
|
||||
$user2->username = "romanb";
|
||||
$user2->name = "Roman";
|
||||
$user2->status = "active";
|
||||
|
||||
$this->_em->persist($user1);
|
||||
$this->_em->persist($user2);
|
||||
$this->_em->persist($group1);
|
||||
$this->_em->persist($group2);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$this->user1 = $this->_em->find(get_class($user1), $user1->id);
|
||||
$this->user2 = $this->_em->find(get_class($user1), $user2->id);
|
||||
}
|
||||
|
||||
public function testClonePersistentCollectionAndReuse()
|
||||
{
|
||||
$user1 = $this->user1;
|
||||
|
||||
$user1->groups = clone $user1->groups;
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$user1 = $this->_em->find(get_class($user1), $user1->id);
|
||||
|
||||
$this->assertEquals(2, count($user1->groups));
|
||||
}
|
||||
|
||||
public function testClonePersistentCollectionAndShare()
|
||||
{
|
||||
$user1 = $this->user1;
|
||||
$user2 = $this->user2;
|
||||
|
||||
$user2->groups = clone $user1->groups;
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$user1 = $this->_em->find(get_class($user1), $user1->id);
|
||||
$user2 = $this->_em->find(get_class($user1), $user2->id);
|
||||
|
||||
$this->assertEquals(2, count($user1->groups));
|
||||
$this->assertEquals(2, count($user2->groups));
|
||||
}
|
||||
|
||||
public function testCloneThenDirtyPersistentCollection()
|
||||
{
|
||||
$user1 = $this->user1;
|
||||
$user2 = $this->user2;
|
||||
|
||||
$group3 = new CmsGroup();
|
||||
$group3->name = "test";
|
||||
$user2->groups = clone $user1->groups;
|
||||
$user2->groups->add($group3);
|
||||
|
||||
$this->_em->persist($group3);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$user1 = $this->_em->find(get_class($user1), $user1->id);
|
||||
$user2 = $this->_em->find(get_class($user1), $user2->id);
|
||||
|
||||
$this->assertEquals(3, count($user2->groups));
|
||||
$this->assertEquals(2, count($user1->groups));
|
||||
}
|
||||
|
||||
public function testNotCloneAndPassAroundFlush()
|
||||
{
|
||||
$user1 = $this->user1;
|
||||
$user2 = $this->user2;
|
||||
|
||||
$group3 = new CmsGroup();
|
||||
$group3->name = "test";
|
||||
$user2->groups = $user1->groups;
|
||||
$user2->groups->add($group3);
|
||||
|
||||
$this->assertEQuals(1, count($user1->groups->getInsertDiff()));
|
||||
|
||||
$this->_em->persist($group3);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$user1 = $this->_em->find(get_class($user1), $user1->id);
|
||||
$user2 = $this->_em->find(get_class($user1), $user2->id);
|
||||
|
||||
$this->assertEquals(3, count($user2->groups));
|
||||
$this->assertEquals(3, count($user1->groups));
|
||||
}
|
||||
}
|
||||
|
||||
103
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1654Test.php
Normal file
103
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1654Test.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
/**
|
||||
* @group DDC-1654
|
||||
*/
|
||||
class DDC1654Test extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
{
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->setUpEntitySchema(array(
|
||||
__NAMESPACE__ . '\\DDC1654Post',
|
||||
__NAMESPACE__ . '\\DDC1654Comment',
|
||||
));
|
||||
}
|
||||
|
||||
public function testManyToManyRemoveFromCollectionOrphanRemoval()
|
||||
{
|
||||
$post = new DDC1654Post();
|
||||
$post->comments[] = new DDC1654Comment();
|
||||
$post->comments[] = new DDC1654Comment();
|
||||
|
||||
$this->_em->persist($post);
|
||||
$this->_em->flush();
|
||||
|
||||
$post->comments->remove(0);
|
||||
$post->comments->remove(1);
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$comments = $this->_em->getRepository(__NAMESPACE__ . '\\DDC1654Comment')->findAll();
|
||||
$this->assertEquals(0, count($comments));
|
||||
}
|
||||
|
||||
public function testManyToManyRemoveElementFromCollectionOrphanRemoval()
|
||||
{
|
||||
$post = new DDC1654Post();
|
||||
$post->comments[] = new DDC1654Comment();
|
||||
$post->comments[] = new DDC1654Comment();
|
||||
|
||||
$this->_em->persist($post);
|
||||
$this->_em->flush();
|
||||
|
||||
$post->comments->removeElement($post->comments[0]);
|
||||
$post->comments->removeElement($post->comments[1]);
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$comments = $this->_em->getRepository(__NAMESPACE__ . '\\DDC1654Comment')->findAll();
|
||||
$this->assertEquals(0, count($comments));
|
||||
}
|
||||
|
||||
public function testManyToManyClearCollectionOrphanRemoval()
|
||||
{
|
||||
$post = new DDC1654Post();
|
||||
$post->comments[] = new DDC1654Comment();
|
||||
$post->comments[] = new DDC1654Comment();
|
||||
|
||||
$this->_em->persist($post);
|
||||
$this->_em->flush();
|
||||
|
||||
$post->comments->clear();
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$comments = $this->_em->getRepository(__NAMESPACE__ . '\\DDC1654Comment')->findAll();
|
||||
$this->assertEquals(0, count($comments));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC1654Post
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="integer") @GeneratedValue
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity="DDC1654Comment", orphanRemoval=true,
|
||||
* cascade={"persist"})
|
||||
*/
|
||||
public $comments = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC1654Comment
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="integer") @GeneratedValue
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
144
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1655Test.php
Normal file
144
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1655Test.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
/**
|
||||
* @group DDC-1655
|
||||
* @group DDC-1640
|
||||
* @group DDC-1556
|
||||
*/
|
||||
class DDC1655Test extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
{
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
try {
|
||||
$this->_schemaTool->createSchema(array(
|
||||
$this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1655Foo'),
|
||||
$this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1655Bar'),
|
||||
$this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1655Baz'),
|
||||
));
|
||||
} catch(\Exception $e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function testPostLoadOneToManyInheritance()
|
||||
{
|
||||
$cm = $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1655Foo');
|
||||
$this->assertEquals(array("postLoad" => array("postLoad")), $cm->lifecycleCallbacks);
|
||||
|
||||
$cm = $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1655Bar');
|
||||
$this->assertEquals(array("postLoad" => array("postLoad", "postSubLoaded")), $cm->lifecycleCallbacks);
|
||||
|
||||
$baz = new DDC1655Baz();
|
||||
$foo = new DDC1655Foo();
|
||||
$foo->baz = $baz;
|
||||
$bar = new DDC1655Bar();
|
||||
$bar->baz = $baz;
|
||||
|
||||
$this->_em->persist($foo);
|
||||
$this->_em->persist($bar);
|
||||
$this->_em->persist($baz);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$baz = $this->_em->find(get_class($baz), $baz->id);
|
||||
foreach ($baz->foos as $foo) {
|
||||
$this->assertEquals(1, $foo->loaded, "should have loaded callback counter incremented for " . get_class($foo));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that post load is not executed several times when the entity
|
||||
* is rehydrated again although its already known.
|
||||
*/
|
||||
public function testPostLoadInheritanceChild()
|
||||
{
|
||||
$bar = new DDC1655Bar();
|
||||
|
||||
$this->_em->persist($bar);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$bar = $this->_em->find(get_class($bar), $bar->id);
|
||||
$this->assertEquals(1, $bar->loaded);
|
||||
$this->assertEquals(1, $bar->subLoaded);
|
||||
|
||||
$bar = $this->_em->find(get_class($bar), $bar->id);
|
||||
$this->assertEquals(1, $bar->loaded);
|
||||
$this->assertEquals(1, $bar->subLoaded);
|
||||
|
||||
$dql = "SELECT b FROM " . __NAMESPACE__ . "\DDC1655Bar b WHERE b.id = ?1";
|
||||
$bar = $this->_em->createQuery($dql)->setParameter(1, $bar->id)->getSingleResult();
|
||||
|
||||
$this->assertEquals(1, $bar->loaded);
|
||||
$this->assertEquals(1, $bar->subLoaded);
|
||||
|
||||
$this->_em->refresh($bar);
|
||||
|
||||
$this->assertEquals(2, $bar->loaded);
|
||||
$this->assertEquals(2, $bar->subLoaded);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("SINGLE_TABLE")
|
||||
* @DiscriminatorMap({
|
||||
* "foo" = "DDC1655Foo",
|
||||
* "bar" = "DDC1655Bar"
|
||||
* })
|
||||
* @HasLifecycleCallbacks
|
||||
*/
|
||||
class DDC1655Foo
|
||||
{
|
||||
/** @Id @GeneratedValue @Column(type="integer") */
|
||||
public $id;
|
||||
|
||||
public $loaded = 0;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="DDC1655Baz", inversedBy="foos")
|
||||
*/
|
||||
public $baz;
|
||||
|
||||
/**
|
||||
* @PostLoad
|
||||
*/
|
||||
public function postLoad()
|
||||
{
|
||||
$this->loaded++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @HasLifecycleCallbacks
|
||||
*/
|
||||
class DDC1655Bar extends DDC1655Foo
|
||||
{
|
||||
public $subLoaded;
|
||||
|
||||
/**
|
||||
* @PostLoad
|
||||
*/
|
||||
public function postSubLoaded()
|
||||
{
|
||||
$this->subLoaded++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC1655Baz
|
||||
{
|
||||
/** @Id @GeneratedValue @Column(type="integer") */
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="DDC1655Foo", mappedBy="baz")
|
||||
*/
|
||||
public $foos = array();
|
||||
}
|
||||
@@ -377,6 +377,7 @@ class ClassMetadataBuilderTest extends \Doctrine\Tests\OrmTestCase
|
||||
array(
|
||||
'user_id' => 'id',
|
||||
),
|
||||
'orphanRemoval' => false,
|
||||
),
|
||||
), $this->cm->associationMappings);
|
||||
}
|
||||
|
||||
@@ -1059,7 +1059,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
|
||||
{
|
||||
$this->assertSqlGeneration(
|
||||
"SELECT t, s, l FROM Doctrine\Tests\Models\DDC117\DDC117Link l INNER JOIN l.target t INNER JOIN l.source s",
|
||||
"SELECT d0_.article_id AS article_id0, d0_.title AS title1, d1_.article_id AS article_id2, d1_.title AS title3 FROM DDC117Link d2_ INNER JOIN DDC117Article d0_ ON d2_.target_id = d0_.article_id INNER JOIN DDC117Article d1_ ON d2_.source_id = d1_.article_id"
|
||||
"SELECT d0_.article_id AS article_id0, d0_.title AS title1, d1_.article_id AS article_id2, d1_.title AS title3, d2_.source_id AS source_id4, d2_.target_id AS target_id5 FROM DDC117Link d2_ INNER JOIN DDC117Article d0_ ON d2_.target_id = d0_.article_id INNER JOIN DDC117Article d1_ ON d2_.source_id = d1_.article_id"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,33 @@ class SchemaValidatorTest extends \Doctrine\Tests\OrmTestCase
|
||||
$ce
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-1587
|
||||
*/
|
||||
public function testValidOneToOneAsIdentifierSchema()
|
||||
{
|
||||
$class1 = $this->em->getClassMetadata(__NAMESPACE__ . '\DDC1587ValidEntity2');
|
||||
$class2 = $this->em->getClassMetadata(__NAMESPACE__ . '\DDC1587ValidEntity1');
|
||||
|
||||
$ce = $this->validator->validateClass($class1);
|
||||
|
||||
$this->assertEquals(array(), $ce);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-1649
|
||||
*/
|
||||
public function testInvalidTripleAssociationAsKeyMapping()
|
||||
{
|
||||
$classThree = $this->em->getClassMetadata(__NAMESPACE__ . '\DDC1649Three');
|
||||
$ce = $this->validator->validateClass($classThree);
|
||||
|
||||
$this->assertEquals(Array(
|
||||
"Cannot map association 'Doctrine\Tests\ORM\Tools\DDC1649Three#two as identifier, because the target entity 'Doctrine\Tests\ORM\Tools\DDC1649Two' also maps an association as identifier.",
|
||||
"The referenced column name 'id' has to be a primary key column on the target entity class 'Doctrine\Tests\ORM\Tools\DDC1649Two'."
|
||||
), $ce);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,3 +181,87 @@ class InvalidEntity2
|
||||
*/
|
||||
protected $assoc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity(repositoryClass="Entity\Repository\Agent")
|
||||
* @Table(name="agent")
|
||||
*/
|
||||
class DDC1587ValidEntity1
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @Id @GeneratedValue
|
||||
* @Column(name="pk", type="integer")
|
||||
*/
|
||||
private $pk;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @Column(name="name", type="string", length=32)
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @var Identifier
|
||||
*
|
||||
* @OneToOne(targetEntity="DDC1587ValidEntity2", cascade={"all"}, mappedBy="agent")
|
||||
* @JoinColumn(name="pk", referencedColumnName="pk_agent")
|
||||
*/
|
||||
private $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table
|
||||
*/
|
||||
class DDC1587ValidEntity2
|
||||
{
|
||||
/**
|
||||
* @var DDC1587ValidEntity1
|
||||
*
|
||||
* @Id
|
||||
* @OneToOne(targetEntity="DDC1587ValidEntity1", inversedBy="identifier")
|
||||
* @JoinColumn(name="pk_agent", referencedColumnName="pk", nullable=false)
|
||||
*/
|
||||
private $agent;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @Column(name="num", type="string", length=16, nullable=true)
|
||||
*/
|
||||
private $num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC1649One
|
||||
{
|
||||
/**
|
||||
* @Id @Column @GeneratedValue
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC1649Two
|
||||
{
|
||||
/** @Id @ManyToOne(targetEntity="DDC1649One")@JoinColumn(name="id", referencedColumnName="id") */
|
||||
public $one;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC1649Three
|
||||
{
|
||||
/** @Id @ManyToOne(targetEntity="DDC1649Two") @JoinColumn(name="id",
|
||||
* referencedColumnName="id") */
|
||||
private $two;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,21 @@ class SetupTest extends \Doctrine\Tests\OrmTestCase
|
||||
$this->originalIncludePath = get_include_path();
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
if ( ! $this->originalIncludePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_include_path($this->originalIncludePath);
|
||||
$loaders = spl_autoload_functions();
|
||||
for ($i = 0; $i < count($loaders); $i++) {
|
||||
if ($i > $this->originalAutoloaderCount+1) {
|
||||
spl_autoload_unregister($loaders[$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testGitAutoload()
|
||||
{
|
||||
Setup::registerAutoloadGit(__DIR__ . "/../../../../../");
|
||||
@@ -92,15 +107,4 @@ class SetupTest extends \Doctrine\Tests\OrmTestCase
|
||||
$this->assertSame($cache, $config->getMetadataCacheImpl());
|
||||
$this->assertSame($cache, $config->getQueryCacheImpl());
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
set_include_path($this->originalIncludePath);
|
||||
$loaders = spl_autoload_functions();
|
||||
for ($i = 0; $i < count($loaders); $i++) {
|
||||
if ($i > $this->originalAutoloaderCount+1) {
|
||||
spl_autoload_unregister($loaders[$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,12 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
|
||||
/** Whether the database schema has already been created. */
|
||||
protected static $_tablesCreated = array();
|
||||
|
||||
/**
|
||||
* Array of entity class name to their tables that were created.
|
||||
* @var array
|
||||
*/
|
||||
protected static $_entityTablesCreated = array();
|
||||
|
||||
/** List of model sets and their classes. */
|
||||
protected static $_modelSets = array(
|
||||
'cms' => array(
|
||||
@@ -235,6 +241,25 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
|
||||
$this->_em->clear();
|
||||
}
|
||||
|
||||
protected function setUpEntitySchema(array $classNames)
|
||||
{
|
||||
if ($this->_em === null) {
|
||||
throw new \RuntimeException("EntityManager not set, you have to call parent::setUp() before invoking this method.");
|
||||
}
|
||||
|
||||
$classes = array();
|
||||
foreach ($classNames as $className) {
|
||||
if ( ! isset(static::$_entityTablesCreated[$className])) {
|
||||
static::$_entityTablesCreated[$className] = true;
|
||||
$classes[] = $this->_em->getClassMetadata($className);
|
||||
}
|
||||
}
|
||||
|
||||
if ($classes) {
|
||||
$this->_schemaTool->createSchema($classes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection to the test database, if there is none yet, and
|
||||
* creates the necessary tables.
|
||||
|
||||
Reference in New Issue
Block a user