Generates a closure capable of finalizing a cloned proxy.

@param PhpcrClassMetadata  $classMetadata
@param \ReflectionProperty $reflectionId

@throws UnexpectedValueException

@return \Closure
546978
private function createCloner(ClassMetadata $classMetadata, ReflectionProperty $reflectionId = null)
    {
        $className = $classMetadata->getName();
        $documentManager = $this->documentManager;

        return function (Proxy $cloned) use ($className, $classMetadata, $documentManager, $reflectionId) {
            if ($cloned->__isInitialized()) {
                return;
            }

            $cloned->__setInitialized(true);
            $cloned->__setInitializer(null);

            if (!$reflectionId) {
                return;
            }

            $original = $documentManager->find($className, $reflectionId->getValue($cloned));

            if (null === $original) {
                throw new UnexpectedValueException(sprintf(
                    'Proxy with ID "%s" could not be loaded',
                    $reflectionId->getValue($cloned)
                ));
            }

            foreach ($classMetadata->getReflectionClass()->getProperties() as $reflectionProperty) {
                $propertyName = $reflectionProperty->getName();

                if ($classMetadata->hasField($propertyName) || $classMetadata->hasAssociation($propertyName)) {
                    $reflectionProperty->setAccessible(true);
                    $reflectionProperty->setValue($cloned, $reflectionProperty->getValue($original));
                }
            }
        };
    }
224520
private function generateProxyMethod(\ReflectionMethod $method): string
    {
        if ($method->isConstructor()) {
            throw new \RuntimeException(sprintf(
                'Can not generate proxy method for constructor in class "%s".',
                $method->getDeclaringClass()->getName()
            ));
        }

        if ($method->isStatic()) {
            throw new \RuntimeException(sprintf(
                'Can not generate proxy method for static method "%s" in class "%s".',
                $method->getName(),
                $method->getDeclaringClass()->getName()
            ));
        }

        if ($method->isPrivate()) {
            throw new \RuntimeException(sprintf(
                'Can not generate proxy for private method "%s" in class "%s".',
                $method->getName(),
                $method->getDeclaringClass()->getName()
            ));
        }

        $proxyMethodParameters = [];
        $methodParameters = [];

        foreach ($method->getParameters() as $methodParameter) {
            $methodParameters[] = '$'.$methodParameter->getName();

            if ($methodParameter->getClass()) {
                $proxyMethodParameter = sprintf(
                    '\\%s %s',
                    $methodParameter->getClass()->getName(),
                    '$'.$methodParameter->getName()
                );
            } else if ($methodParameter->getType()) {
                $proxyMethodParameter = \sprintf(
                    '%s%s $%s',
                    $methodParameter->getType()->allowsNull() ? '?' : '',
                    $methodParameter->getType()->getName(),
                    $methodParameter->getName()
                );
            } else {
                $proxyMethodParameter = '$'.$methodParameter->getName();
            }

            if ($methodParameter->isOptional()) {
                $proxyMethodParameter .= ' = '.\var_export($methodParameter->getDefaultValue(), true);
            }

            $proxyMethodParameters[] = $proxyMethodParameter;
        }

        if ($method->isPublic()) {
            $availability = 'public';
        } else if ($method->isProtected()) {
            $availability = 'protected';
        } else {
            throw new \RuntimeException(sprintf(
                'Not support availability for method "%s" in class "%s".',
                $method->getName(),
                $method->getDeclaringClass()->getName()
            ));
        }

        $docComment = "/**\n * {@inheritdoc}\n */";
        $returnTypeHint = '';
        $returnResult = "\n\n    return \$result;";

        if ($method->getReturnType()) {
            $returnTypeHint = sprintf(
                ': %s%s%s',
                \class_exists($method->getReturnType()->getName()) ? '\\' : '',
                $method->getReturnType()->allowsNull() ? '?' : '',
                $method->getReturnType()->getName()
            );
        }

        if ($method->getReturnType() && 'void' === $method->getReturnType()->getName()) {
            $returnResult = '';
        }

        $templateVariables = [
            'docComment'            => $docComment,
            'availability'          => $availability,
            'name'                  => $method->getName(),
            'proxyMethodParameters' => \implode(', ', $proxyMethodParameters),
            'parameters'            => \implode(', ', $methodParameters),
            'beginArguments'        => null,
            'rollbackArguments'     => null,
            'commitArguments'       => null,
            'return_type_hint'      => $returnTypeHint,
            'return_result'         => $returnResult,
        ];

        $methodTemplate = $this->getTemplateForProxyMethod();
        $methodCode = $this->replaceVariables($methodTemplate, $templateVariables);
        $methodCode = $this->appendTabulationCharacter($methodCode, 1);

        return $methodCode;
    }
1110868
public function create(ClassReflection $classReflection, \ReflectionMethod $methodReflection, string $methodWrapper = null): MethodReflection
    {
        $phpDocParameterTypes = [];
        $phpDocReturnType = null;
        if ($methodReflection->getDocComment() !== false) {
            $phpDocBlock = PhpDocBlock::resolvePhpDocBlockForMethod(
                Broker::getInstance(),
                $methodReflection->getDocComment(),
                $methodReflection->getDeclaringClass()->getName(),
                $methodReflection->getName(),
                $methodReflection->getFileName()
            );

            $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc(
                $phpDocBlock->getFile(),
                $phpDocBlock->getClass(),
                $phpDocBlock->getDocComment()
            );
            $phpDocParameterTypes = array_map(function (ParamTag $tag): Type {
                return $tag->getType();
            }, $resolvedPhpDoc->getParamTags());
            $phpDocReturnType = $resolvedPhpDoc->getReturnTag() !== null ? $resolvedPhpDoc->getReturnTag()->getType() : null;
        }

        if ($methodWrapper) {
            $methodReflection = new $methodWrapper($methodReflection);
        }

        return $this->methodReflectionFactory->create(
            $classReflection,
            $methodReflection,
            $phpDocParameterTypes,
            $phpDocReturnType
        );
    }
577425
public static function getHub($classMetadata, $propertyName)
    {
        if ($propertyName instanceof ReflectionProperty) {
            $propertyName = $propertyName->getName();
        }
        $path = self::PATH_HUB;

        return $classMetadata->$path[$propertyName];
    }
1180658
public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator)
    {
        CanProxyAssertion::assertClassCanBeProxied($originalClass);

        $publicProperties = new PublicPropertiesMap(Properties::fromReflectionClass($originalClass));
        $interfaces       = [AccessInterceptorValueHolderInterface::class];

        if ($originalClass->isInterface()) {
            $interfaces[] = $originalClass->getName();
        } else {
            $classGenerator->setExtendedClass($originalClass->getName());
        }

        $classGenerator->setImplementedInterfaces($interfaces);
        $classGenerator->addPropertyFromGenerator($valueHolder        = new ValueHolderProperty($originalClass));
        $classGenerator->addPropertyFromGenerator($prefixInterceptors = new MethodPrefixInterceptors());
        $classGenerator->addPropertyFromGenerator($suffixInterceptors = new MethodSuffixInterceptors());
        $classGenerator->addPropertyFromGenerator($publicProperties);

        array_map(
            static function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator) : void {
                ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod);
            },
            array_merge(
                array_map(
                    $this->buildMethodInterceptor($prefixInterceptors, $suffixInterceptors, $valueHolder),
                    ProxiedMethodsFilter::getProxiedMethods($originalClass)
                ),
                [
                    Constructor::generateMethod($originalClass, $valueHolder),
                    new StaticProxyConstructor($originalClass, $valueHolder, $prefixInterceptors, $suffixInterceptors),
                    new GetWrappedValueHolderValue($valueHolder),
                    new SetMethodPrefixInterceptor($prefixInterceptors),
                    new SetMethodSuffixInterceptor($suffixInterceptors),
                    new MagicGet(
                        $originalClass,
                        $valueHolder,
                        $prefixInterceptors,
                        $suffixInterceptors,
                        $publicProperties
                    ),
                    new MagicSet(
                        $originalClass,
                        $valueHolder,
                        $prefixInterceptors,
                        $suffixInterceptors,
                        $publicProperties
                    ),
                    new MagicIsset(
                        $originalClass,
                        $valueHolder,
                        $prefixInterceptors,
                        $suffixInterceptors,
                        $publicProperties
                    ),
                    new MagicUnset(
                        $originalClass,
                        $valueHolder,
                        $prefixInterceptors,
                        $suffixInterceptors,
                        $publicProperties
                    ),
                    new MagicClone($originalClass, $valueHolder, $prefixInterceptors, $suffixInterceptors),
                    new MagicSleep($originalClass, $valueHolder),
                    new MagicWakeup($originalClass),
                ]
            )
        );
    }
600474
public function generateProxyCode(\ReflectionClass $ref)
	{
		$methods = $this->collectProxyMethods($ref);
		$implements = ['\KoolKode\Context\Scope\ScopedProxyInterface'];
				
		$code = 'namespace ' . $ref->getNamespaceName() . ' { ';
		$code .= 'if(!class_exists(' . var_export($ref->name . '__scoped', true) . ', false)) { ';
		$code .= 'final class ' . $ref->getShortName() . '__scoped';
		
		if($ref->isInterface())
		{
			$implements[] = '\\' . $ref->name;
		}
		else
		{
			$code .= ' extends \\' . $ref->name;
		}
		
		$code .= ' implements ' . implode(', ', $implements) . ' { ';
		$code .= 'use \KoolKode\Context\Scope\ScopedProxyTrait; ';
		
		$code .= 'private $__K2Binding, $__K2Scope, $__K2Target; ';
		
		$code .= 'public final function __construct(\KoolKode\Context\Bind\BindingInterface $binding, \KoolKode\Context\Scope\ScopeManagerInterface $scope, \SplObjectStorage & $target) { ';
		$code .= '$this->__K2Binding = $binding; $this->__K2Scope = $scope; $this->__K2Target = & $target; ';
		
		$fieldNames = array_unique($this->collectRemovedFieldNames($ref));
		
		if(!empty($fieldNames))
		{
			$code .= ' unset(';
			
			foreach($fieldNames as $i => $name)
			{
				if($i != 0)
				{
					$code .= ', ';
				}
				
				$code .= '$this->' . $name;
			}
			
			$code .= ');';
		}
		
		$code.= ' } ';
		
		foreach($methods as $method)
		{
			$code .= $this->buildMethodSignature($method, true);
			
			$code .= ' { ';
			$code .= 'if(!$this->__K2Target->offsetExists($this)) { $this->__K2Scope->activateInstance($this); } ';
			$code .= 'return call_user_func_array([$this->__K2Target[$this], __FUNCTION__], func_get_args()); ';
			$code .= '} ';
		}
		
		return $code . ' } } } ';
	}
918059
protected function preparePropertyName(\ReflectionMethod $reflectionMethod)
    {
        $currentPrefix = $this->parameters[static::CURRENT_PREFIX];

         // Get the name and remove the 'get' . . .
        $getterName = $reflectionMethod->getName();
        if (strpos($getterName, $currentPrefix) === 0) {
            return lcfirst(substr($getterName, strlen($currentPrefix)));
        }

        // . . .  or the '_get'.
        if (strpos($getterName, '_' . $currentPrefix) === 0) {
            return lcfirst(substr($getterName, strlen($currentPrefix) + 1));
        }

        // Still here?!? At least make the first letter lowercase.
        return lcfirst($getterName);
    }
1322177
public function wrapService($serviceId, $instance)
    {
        $serviceId = $this->canonicalizeName($serviceId);
        $proxy = null;
        
        if (($fqcn = $this->getCache()->getItem(self::CACHE_ID_PREFIX . $serviceId)) 
             && class_exists($fqcn) 
             && $this->getProxyAutoCreateStrategy() !== self::PROXY_AUTO_CREATE_ALWAYS) {
            
            $proxy = new $fqcn($instance);
        } else {
            $className      = get_class($instance);
            $proxyGenerator = $this->getProxyGenerator();
            $fqcn           = $proxyGenerator->getProxyClassName($className);
            $file           = $proxyGenerator->getProxyFilename($className);
            
            // Re-generate proxy class if original service class file has changed
            if ($this->getProxyAutoCreateStrategy() === self::PROXY_AUTO_CREATE_MTIME && file_exists($file)) {
                $reflection = new ReflectionClass($className);
                $instanceFile = $reflection->getFileName();
                
                if (filemtime($instanceFile) > filemtime($file)) {
                    unlink($file);
                }
            }

            if (!file_exists($file) || $this->getProxyAutoCreateStrategy() === self::PROXY_AUTO_CREATE_ALWAYS) {
                try {
                    $config = $this->getAnnotationBuilder()->getServiceSpecification($instance);
                } catch(\Exception $e) {
                    throw new AnnotationException(
                        sprintf('Unable to parse annotations for service %s (%s). Reason: %s', $serviceId, $file, $e->getMessage()));
                }
                
                $config['service_id'] = $serviceId; // Overwrite any previously configured service ID
                
                $proxyGenerator->generateProxyClass($instance, $config);
            }
            
            require_once $file;
            $proxy = new $fqcn($instance);
            
            $this->getCache()->setItem(self::CACHE_ID_PREFIX . $serviceId, $fqcn);
        }
        
        // Run initializers for the proxy class
        foreach ($this->initializers as $initializer) {
            if ($initializer instanceof InitializerInterface) {
                $initializer->initialize($proxy, $this);
            } elseif (is_object($initializer) && is_callable($initializer)) {
                $initializer($proxy, $this);
            } else {
                call_user_func($initializer, $proxy, $this);
            }
        }
        
        return $proxy;
    }
494481
private function createCloner(
        ClassMetadata $classMetadata,
        DocumentPersister $documentPersister
    )
    {
        return function (BaseProxy $proxy) use ($documentPersister, $classMetadata) {
            if ($proxy->__isInitialized()) {
                return;
            }

            $proxy->__setInitialized(true);
            $proxy->__setInitializer(null);

            $identifier = $classMetadata->createIdentifier($proxy);
            $original   = $documentPersister->load($identifier);

            if ($original === null) {
                if (!$this->lifecycleEventManager->documentNotFound($proxy, $identifier)) {
                    throw DocumentNotFoundException::documentNotFound(get_class($proxy), $identifier);
                }
            }

            foreach ($classMetadata->getReflectionClass()->getProperties() as $reflectionProperty) {
                $propertyName = $reflectionProperty->getName();

                if ($classMetadata->hasField($propertyName) || $classMetadata->hasAssociation($propertyName)) {
                    $reflectionProperty->setAccessible(true);
                    $reflectionProperty->setValue($proxy, $reflectionProperty->getValue($original));
                }
            }
        };
    }
127859
protected function doLoadMetadata(
        string $className,
        ?ClassMetadata $parent,
        ClassMetadataBuildingContext $metadataBuildingContext
    ) : ClassMetadata {
        $classMetadata = new ClassMetadata($className, $metadataBuildingContext);

        if ($parent) {
            $classMetadata->setParent($parent);

            foreach ($parent->getDeclaredPropertiesIterator() as $fieldName => $property) {
                $classMetadata->addInheritedProperty($property);
            }

            $classMetadata->setInheritanceType($parent->inheritanceType);
            $classMetadata->setIdentifier($parent->identifier);

            if ($parent->discriminatorColumn) {
                $classMetadata->setDiscriminatorColumn($parent->discriminatorColumn);
                $classMetadata->setDiscriminatorMap($parent->discriminatorMap);
            }

            $classMetadata->setLifecycleCallbacks($parent->lifecycleCallbacks);
            $classMetadata->setChangeTrackingPolicy($parent->changeTrackingPolicy);

            if ($parent->isMappedSuperclass) {
                $classMetadata->setCustomRepositoryClassName($parent->getCustomRepositoryClassName());
            }
        }

        // Invoke driver
        try {
            $this->driver->loadMetadataForClass($classMetadata->getClassName(), $classMetadata, $metadataBuildingContext);
        } catch (ReflectionException $e) {
            throw MappingException::reflectionFailure($classMetadata->getClassName(), $e);
        }

        $this->completeIdentifierGeneratorMappings($classMetadata);

        if ($parent) {
            if ($parent->getCache()) {
                $classMetadata->setCache(clone $parent->getCache());
            }

            if (! empty($parent->entityListeners) && empty($classMetadata->entityListeners)) {
                $classMetadata->entityListeners = $parent->entityListeners;
            }
        }

        $this->completeRuntimeMetadata($classMetadata, $parent);

        if ($this->evm->hasListeners(Events::loadClassMetadata)) {
            $eventArgs = new LoadClassMetadataEventArgs($classMetadata, $this->em);

            $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
        }

        $this->buildValueGenerationPlan($classMetadata);
        $this->validateRuntimeMetadata($classMetadata, $parent);

        return $classMetadata;
    }
907492
public static function getHubReflection($classMetadata, $propertyName)
    {
        $result = self::getHub($classMetadata, $propertyName);

        return $result[self::REFLECTION];
    }
856633
private static function forkCallMapClass(\ReflectionClass $class): \ReflectionClass
    {
        if ($class->isTrait()) {
            $class = self::forkTrait($class);
        }

        try {
            $compile = self::$compile;
            $compile(self::createCallmapProxyCode($class));
        } catch (\ParseError $pe) {
            throw new ProxyCreationFailure(
                    'Failure while creating CallMap instance of '
                    . $class->getName() . ': ' . $pe->getMessage(),
                    $pe
            );
        }

        return new \ReflectionClass($class->getName() . 'CallMapProxy');
    }
264279
private function loadMaskProperty($reflectionProperty, $classMetadata)
    {
        /** @var Mask $maskAnnotation */
        $maskAnnotation = self::$defaultAnnotationReader->getPropertyAnnotation(
            $reflectionProperty,
            Mask::class
        );

        if (!$maskAnnotation) {
            return;
        }

        // normalize bit list
        $maskAnnotation->bits = BitNormalizer::getNormalized(
            $maskAnnotation->bits
        );

        MetadataLayer::setMask($classMetadata, $maskAnnotation, $reflectionProperty);
    }
393775
public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator)
    {
        CanProxyAssertion::assertClassCanBeProxied($originalClass);

        $interfaces = [RemoteObjectInterface::class];

        if ($originalClass->isInterface()) {
            $interfaces[] = $originalClass->getName();
        } else {
            $classGenerator->setExtendedClass($originalClass->getName());
        }

        $classGenerator->setImplementedInterfaces($interfaces);
        $classGenerator->addPropertyFromGenerator($adapter = new AdapterProperty());

        array_map(
            static function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator) : void {
                ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod);
            },
            array_merge(
                array_map(
                    static function (ReflectionMethod $method) use ($adapter, $originalClass) : RemoteObjectMethod {
                        return RemoteObjectMethod::generateMethod(
                            new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()),
                            $adapter,
                            $originalClass
                        );
                    },
                    ProxiedMethodsFilter::getProxiedMethods(
                        $originalClass,
                        ['__get', '__set', '__isset', '__unset']
                    )
                ),
                [
                    new StaticProxyConstructor($originalClass, $adapter),
                    new MagicGet($originalClass, $adapter),
                    new MagicSet($originalClass, $adapter),
                    new MagicIsset($originalClass, $adapter),
                    new MagicUnset($originalClass, $adapter),
                ]
            )
        );
    }
457142
protected function buildMethod(ReflectionMethod $reflectionMethod) : Node\Stmt\ClassMethod
    {
        $methodBuilder = new Method($reflectionMethod->getName());

        if ($reflectionMethod->isPublic()) {
            $methodBuilder->makePublic();
        }

        if ($reflectionMethod->isProtected()) {
            $methodBuilder->makeProtected();
        }

        if ($reflectionMethod->isPrivate()) {
            $methodBuilder->makePrivate();
        }

        if ($reflectionMethod->isStatic()) {
            $methodBuilder->makeStatic();
        }

        if ($reflectionMethod->isAbstract()) {
            $methodBuilder->makeAbstract();
        }

        if ($reflectionMethod->isFinal()) {
            $methodBuilder->makeFinal();
        }

        if ($reflectionMethod->returnsReference()) {
            $methodBuilder->makeReturnByRef();
        }

        foreach ($reflectionMethod->getParameters() as $reflectionParameter) {
            $methodBuilder->addParam($this->buildParameter($reflectionParameter));
        }

        // @todo should parse method body if possible (skipped for now)

        return $methodBuilder->getNode();
    }
222551
public function generate(\ReflectionClass $originalClass, PhpClass $class)
    {
        $methods = ReflectionUtils::getOverrideableMethods($originalClass, true);

        // no public, non final methods
        if (empty($methods)) {
            return;
        }

        if (null !== $this->markerInterface) {
            $class->setImplementedInterfaces(array_merge(
                $class->getImplementedInterfaces(),
                array($this->markerInterface)
            ));
        }

        $initializer = new PhpProperty();
        $initializer->setName($this->prefix.'lazyInitializer');
        $initializer->setVisibility(PhpProperty::VISIBILITY_PRIVATE);
        $class->setProperty($initializer);

        $initialized = new PhpProperty();
        $initialized->setName($this->prefix.'initialized');
        $initialized->setDefaultValue(false);
        $initialized->setVisibility(PhpProperty::VISIBILITY_PRIVATE);
        $class->setProperty($initialized);

        $initializerSetter = new PhpMethod();
        $initializerSetter->setName($this->prefix.'setLazyInitializer');
        $initializerSetter->setBody('$this->'.$this->prefix.'lazyInitializer = $initializer;');

        $parameter = new PhpParameter();
        $parameter->setName('initializer');
        $parameter->setType('\CG\Proxy\LazyInitializerInterface');
        $initializerSetter->addParameter($parameter);
        $class->setMethod($initializerSetter);

        $this->addMethods($class, $methods);

        $initializingMethod = new PhpMethod();
        $initializingMethod->setName($this->prefix.'initialize');
        $initializingMethod->setVisibility(PhpMethod::VISIBILITY_PRIVATE);
        $initializingMethod->setBody(
            $this->writer
                ->reset()
                ->writeln('if (null === $this->'.$this->prefix.'lazyInitializer) {')
                    ->indent()
                    ->writeln('throw new \RuntimeException("'.$this->prefix.'setLazyInitializer() must be called prior to any other public method on this object.");')
                    ->outdent()
                ->write("}\n\n")
                ->writeln('$this->'.$this->prefix.'lazyInitializer->initializeObject($this);')
                ->writeln('$this->'.$this->prefix.'initialized = true;')
                ->getContent()
        );
        $class->setMethod($initializingMethod);
    }
113830
public function computeChangeSet(ClassMetadata $class, $document)
    {
        if ($document instanceof Proxy && !$document->__isInitialized()) {
            return;
        }

        $oid = spl_object_hash($document);
        if (in_array($oid, $this->changesetComputed)) {
            return;
        }

        $this->changesetComputed[] = $oid;

        $changeSet = $actualData = $this->getDocumentActualData($class, $document);
        $id = $this->getDocumentId($document, false);
        $isNew = !isset($this->originalData[$oid]);

        if ($isNew) {
            // Document is New and should be inserted
            $this->originalData[$oid] = $changeSet;
        } elseif (!empty($this->documentChangesets[$oid]['fields'])) {
            foreach ($this->documentChangesets[$oid]['fields'] as $fieldName => $data) {
                $this->originalData[$oid][$fieldName] = $data[0];
            }
        }

        if ($class->parentMapping && isset($changeSet[$class->parentMapping])) {
            $parent = $changeSet[$class->parentMapping];
            $parentClass = $this->dm->getClassMetadata(get_class($parent));
            $state = $this->getDocumentState($parent);

            if ($state === self::STATE_MANAGED) {
                $this->computeChangeSet($parentClass, $parent);
            }
        }

        foreach ($class->childMappings as $fieldName) {
            if ($changeSet[$fieldName]) {
                if (is_array($changeSet[$fieldName]) || $changeSet[$fieldName] instanceof Collection) {
                    throw PHPCRException::childFieldIsArray(
                        self::objToStr($document, $this->dm),
                        $fieldName
                    );
                }

                if (!is_object($changeSet[$fieldName])) {
                    throw PHPCRException::childFieldNoObject(
                        self::objToStr($document, $this->dm),
                        $fieldName,
                        gettype($changeSet[$fieldName])
                    );
                }

                $mapping = $class->mappings[$fieldName];
                $changeSet[$fieldName] = $this->computeChildChanges($mapping, $changeSet[$fieldName], $id, $mapping['nodeName']);
            }
        }

        $this->computeAssociationChanges($document, $class, $oid, $isNew, $changeSet, 'reference');
        $this->computeAssociationChanges($document, $class, $oid, $isNew, $changeSet, 'referrer');

        foreach ($class->mixedReferrersMappings as $fieldName) {
            if ($changeSet[$fieldName]
                && $changeSet[$fieldName] instanceof PersistentCollection
                && $changeSet[$fieldName]->isDirty()
            ) {
                throw new PHPCRException("The immutable mixed referrer collection in field $fieldName is dirty");
            }
        }

        $this->computeChildrenChanges($document, $class, $oid, $isNew, $changeSet);

        if (!$isNew) {
            // collect assignment move operations
            $destPath = $destName = false;

            if (isset($this->originalData[$oid][$class->parentMapping], $changeSet[$class->parentMapping])
                && $this->originalData[$oid][$class->parentMapping] !== $changeSet[$class->parentMapping]
            ) {
                $destPath = $this->getDocumentId($changeSet[$class->parentMapping]);
            }

            if (isset($this->originalData[$oid][$class->nodename], $changeSet[$class->nodename])
                && $this->originalData[$oid][$class->nodename] !== $changeSet[$class->nodename]
            ) {
                $destName = $changeSet[$class->nodename];
            }

            // there was assignment move
            if ($destPath || $destName) {
                // add the other field if only one was changed
                if (false === $destPath) {
                    $destPath = isset($changeSet[$class->parentMapping])
                        ? $this->getDocumentId($changeSet[$class->parentMapping])
                        : PathHelper::getParentPath($this->getDocumentId($document));
                }
                if (false === $destName) {
                    $destName = $class->nodename !== null && $changeSet[$class->nodename]
                        ? $changeSet[$class->nodename]
                        : PathHelper::getNodeName($this->getDocumentId($document));
                }

                // make sure destination nodename is okay
                if ($exception = $class->isValidNodename($destName)) {
                    throw IdException::illegalName($document, $class->nodename, $destName);
                }

                // prevent path from becoming "//foobar" when moving to root node.
                $targetPath = ('/' == $destPath) ? "/$destName" : "$destPath/$destName";

                $this->scheduleMove($document, $targetPath);
            }

            if (isset($this->originalData[$oid][$class->identifier], $changeSet[$class->identifier])
                && $this->originalData[$oid][$class->identifier] !== $changeSet[$class->identifier]
            ) {
                throw new PHPCRException('The Id is immutable ('.$this->originalData[$oid][$class->identifier].' !== '.$changeSet[$class->identifier].'). Please use DocumentManager::move to move the document: '.self::objToStr($document, $this->dm));
            }
        }

        $fields = array_intersect_key($changeSet, $class->mappings);

        if ($this->isDocumentTranslatable($class)) {
            $locale = $this->getCurrentLocale($document, $class);

            // ensure we do not bind a previously removed translation
            if (!$this->isTranslationRemoved($document, $locale)) {
                $this->doBindTranslation($document, $locale, $class);
            }
        }

        if ($isNew) {
            $this->documentChangesets[$oid]['fields'] = $fields;
            $this->scheduledInserts[$oid] = $document;

            return;
        }

        $translationChanges = false;
        if ($this->isDocumentTranslatable($class)) {
            $oid = spl_object_hash($document);
            if (isset($this->documentTranslations[$oid])) {
                foreach ($this->documentTranslations[$oid] as $localeToCheck => $data) {
                    // a translation was removed
                    if (empty($data)) {
                        $translationChanges = true;
                        break;
                    }
                    // a translation was added
                    if (empty($this->originalTranslatedData[$oid][$localeToCheck])) {
                        $translationChanges = true;
                        break;
                    }
                    // a translation was changed
                    foreach ($data as $fieldName => $fieldValue) {
                        if ($this->originalTranslatedData[$oid][$localeToCheck][$fieldName] !== $fieldValue) {
                            $translationChanges = true;
                            break;
                        }
                    }
                }
            }

            // ensure that locale changes are not considered a change in the document
            if ($class->localeMapping && array_key_exists($class->localeMapping, $fields)) {
                unset($fields[$class->localeMapping]);
            }
        }

        foreach ($fields as $fieldName => $fieldValue) {
            $keepChange = false;
            if ($fieldValue instanceof ReferenceManyCollection || $fieldValue instanceof ReferrersCollection) {
                if ($fieldValue->changed()) {
                    $keepChange = true;
                }
            } elseif ($this->originalData[$oid][$fieldName] !== $fieldValue) {
                $keepChange = true;
            }

            if ($keepChange) {
                $fields[$fieldName] = [$this->originalData[$oid][$fieldName], $fieldValue];
            } else {
                unset($fields[$fieldName]);
            }
        }

        if (!empty($fields) || $translationChanges) {
            $this->documentChangesets[$oid]['fields'] = $fields;
            $this->originalData[$oid] = $actualData;
            $this->scheduledUpdates[$oid] = $document;
        } elseif (empty($this->documentChangesets[$oid]['reorderings'])) {
            unset($this->documentChangesets[$oid], $this->scheduledUpdates[$oid]);
        } else {
            $this->documentChangesets[$oid]['fields'] = [];
        }
    }