• Модуль: main
  • Путь к файлу: ~/bitrix/modules/main/lib/orm/query/result.php
  • Класс: BitrixMainORMQueryResult
  • Вызов: Result::fetchObject
public function fetchObject()
{
	// TODO when join, add primary and hide it in ARRAY result, but use for OBJECT fetch
	// e.g. when first fetchObject, remove data modifier that cuts 'unexpected' primary fields

	// TODO wakeup reference objects with only primary if there are enough data in result

	// base object initialization
	$this->initializeFetchObject();

	// array data
	$row = $this->result->fetch();

	if (empty($row))
	{
		return null;
	}

	if (is_object($row) && $row instanceof EntityObject)
	{
		// all rows has already been fetched in initializeFetchObject
		return $row;
	}

	// get primary of base object
	$basePrimaryValues = [];

	foreach ($this->primaryAliases as $primaryName => $primaryAlias)
	{
		/** @var ScalarField $primaryField */
		$primaryField = $this->query->getEntity()->getField($primaryName);
		$primaryValue = $primaryField->cast($row[$primaryAlias]);

		$basePrimaryValues[$primaryName] = $primaryValue;
	}

	// check for object in identity map
	$baseAddToIM = false;
	$objectClass = $this->objectClass;

	/** @var EntityObject $object */
	$object = $this->identityMap->get($objectClass, $basePrimaryValues);

	if (empty($object))
	{
		$object = new $objectClass(false);

		// set right state
		$object->sysChangeState(State::ACTUAL);

		// add to identityMap later, when primary is set
		$baseAddToIM = true;
	}

	/** @var EntityObject[] $relEntityCache Last reference and relation object that has been woken up by definition */
	$relEntityCache = [];

	// go through select chains
	foreach ($this->query->getSelectChains() as $selectChain)
	{
		// object for current chain element, for the first element is object of init entity
		$currentObject = $object;

		// accumulated definition from the first to the current chain element
		$currentDefinitionParts = [];
		$currentDefinition = null;

		// cut first element as long as it is init entity
		$iterableElements = array_slice($selectChain->getAllElements(), 1);

		// dive deep from the start to the end of chain
		foreach ($iterableElements as $element)
		{
			if ($currentObject === null)
			{
				continue;
			}

			/** @var $element ChainElement $field */
			$field = $element->getValue();

			if (!($field instanceof Field))
			{
				// ignore old-style back references, OneToMany is expected instead
				// skip for the next chain
				continue 2;
			}

			// actualize current definition
			$currentDefinitionParts[] = $field->getName();
			$currentDefinition = join('.', $currentDefinitionParts);

			// is it runtime field? then ->tmpSet()
			$isRuntimeField = !empty($this->query->getRuntimeChains()[$currentDefinition]);

			if ($field instanceof IReadable)
			{
				// for remote objects all values have been already set during compose
				if ($currentObject !== $object)
				{
					continue;
				}

				// normalize value
				$value = $field->cast($row[$selectChain->getAlias()]);

				// set value as actual to the object
				$isRuntimeField
					? $currentObject->sysSetRuntime($field->getName(), $value)
					: $currentObject->sysSetActual($field->getName(), $value);
			}
			else
			{
				// define remote entity definition
				// check if this reference has already been woken up
				// main part of current chain (w/o last element) should be the same
				if (array_key_exists($currentDefinition, $relEntityCache))
				{
					$currentObject = $relEntityCache[$currentDefinition];
					continue;
				} // else it will be set after object identification

				// define remote entity of reference
				$remoteEntity = $field->getRefEntity();

				// define values and primary of remote object
				// we can set all values at one time and skip other iterations with values of this object
				$remotePrimary = $remoteEntity->getPrimaryArray();
				$remoteObjectValues = [];
				$remotePrimaryValues = [];

				foreach ($this->selectChainsMap[$currentDefinition] as $remoteChain)
				{
					/** @var ScalarField|ExpressionField $remoteField */
					$remoteField = $remoteChain->getLastElement()->getValue();
					$remoteValue = $row[$remoteChain->getAlias()];

					$remoteObjectValues[$remoteField->getName()] = $remoteValue;
				}

				foreach ($remotePrimary as $primaryName)
				{
					if (!array_key_exists($primaryName, $remoteObjectValues))
					{
						throw new SystemException(sprintf(
							'Primary of %s was not found in database result', $remoteEntity->getDataClass()
						));
					}

					$remotePrimaryValues[$primaryName] = $remoteObjectValues[$primaryName];
				}

				// compose relative object
				if ($field instanceof Reference)
				{
					// get object via identity map
					$remoteObject = $this->composeRemoteObject($remoteEntity, $remotePrimaryValues, $remoteObjectValues);

					// set remoteObject to baseObject
					$isRuntimeField
						? $currentObject->sysSetRuntime($field->getName(), $remoteObject)
						: $currentObject->sysSetActual($field->getName(), $remoteObject);
				}
				elseif ($field instanceof OneToMany || $field instanceof ManyToMany)
				{
					// get collection of remote objects
					if ($isRuntimeField)
					{
						if (empty($currentObject->sysGetRuntime($field->getName())))
						{
							// create new collection and set as value for current object
							/** @var Collection $collection */
							$collection = $remoteEntity->createCollection();
							$currentObject->sysSetRuntime($field->getName(), $collection);
						}
						else
						{
							$collection = $currentObject->sysGetRuntime($field->getName());
						}
					}
					else
					{
						if (empty($currentObject->sysGetValue($field->getName())))
						{
							// create new collection and set as value for current object
							/** @var Collection $collection */
							$collection = $remoteEntity->createCollection();

							// collection should be filled if there are no LIMIT and relation filter in query
							if ($this->query->getLimit() === null)
							{
								// noting in filter should start with $currentDefinition
								$noRelationInFilter = true;

								foreach ($this->query->getFilterChains() as $chain)
								{
									if (strpos($chain->getDefinition(), $currentDefinition) === 0)
									{
										$noRelationInFilter = false;
										break;
									}
								}

								if ($noRelationInFilter)
								{
									// now we are sure the set is complete
									$collection->sysSetFilled();
								}
							}

							$currentObject->sysSetActual($field->getName(), $collection);
						}
						else
						{
							$collection = $currentObject->sysGetValue($field->getName());
						}
					}

					// define remote object
					if (current($remotePrimaryValues) === null || !$collection->hasByPrimary($remotePrimaryValues))
					{
						// get object via identity map
						$remoteObject = $this->composeRemoteObject($remoteEntity, $remotePrimaryValues, $remoteObjectValues);

						// add to collection
						if ($remoteObject !== null)
						{
							$collection->sysAddActual($remoteObject);
						}
					}
					else
					{
						$remoteObject = $collection->getByPrimary($remotePrimaryValues);
					}
				}
				else
				{
					throw new SystemException('Unknown chain element value while fetching object');
				}

				// switch current object, further chain elements belong to this object
				$currentObject = $remoteObject;

				// save as ready object for current row
				$relEntityCache[$currentDefinition] = $remoteObject;
			}
		}
	}

	if ($baseAddToIM)
	{
		// save to identityMap
		$this->identityMap->put($object);
	}

	return $object;
}