Your IP : 18.222.120.124


Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/orm/query/
Upload File :
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/orm/query/chain.php

<?php

namespace Bitrix\Main\ORM\Query;

use Bitrix\Main;
use Bitrix\Main\ORM\Entity;
use Bitrix\Main\ORM\Fields\ExpressionField;
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Fields\Relations\ManyToMany;
use Bitrix\Main\ORM\Fields\Relations\OneToMany;
use Bitrix\Main\SystemException;

class Chain
{
	/** @var ChainElement[] */
	protected $chain;

	protected $size = 0;

	/** @var string[] Definition caches ([0] - full length) */
	protected $definition;

	/** @var array Definition cache */
	protected $definitionParts;

	protected $alias;

	protected $custom_alias;

	/** @var boolean */
	protected $forcesDataDoublingOff = false;

	/** @var ChainElement */
	protected $last_element;

	public function __construct()
	{
		$this->chain = array();
	}

	/**
	 * @param ChainElement $element
	 *
	 * @throws SystemException
	 */
	public function addElement(ChainElement $element)
	{
		if (empty($this->chain) && !($element->getValue() instanceof Entity))
		{
			throw new SystemException('The first element of chain should be Entity only.');
		}

		$this->chain[] = $element;
		$this->definition = null;
		$this->definitionParts = null;
		$this->alias = null;

		$this->last_element = $element;
		$this->size++;
	}

	/**
	 * @param ChainElement $element
	 */
	public function prependElement(ChainElement $element)
	{
		$this->chain = array_merge(array($element), $this->chain);
		$this->definition = null;
		$this->definitionParts = null;
		$this->alias = null;

		$this->size++;
	}

	/**
	 * @param Chain $chain
	 */
	public function prepend(Chain $chain)
	{
		$elements = $chain->getAllElements();

		for ($i=count($elements)-1; $i>=0; $i--)
		{
			$this->prependElement($elements[$i]);
		}
	}

	public function getFirstElement()
	{
		return $this->chain[0];
	}

	/**
	 * @return ChainElement
	 */
	public function getLastElement()
	{
		return $this->last_element;
	}

	/**
	 * @return array|ChainElement[]
	 */
	public function getAllElements()
	{
		return $this->chain;
	}

	public function removeLastElement()
	{
		$this->chain = array_slice($this->chain, 0, -1);
		$this->definition = null;
		$this->definitionParts = null;
		$this->alias = null;

		$this->last_element = end($this->chain);
		$this->size--;
	}

	public function removeFirstElement()
	{
		$this->chain = array_slice($this->chain, 1);
		$this->definition = null;
		$this->definitionParts = null;
		$this->alias = null;

		$this->size--;
	}

	public function hasBackReference()
	{
		foreach ($this->chain as $element)
		{
			if ($element->isBackReference())
			{
				return true;
			}
		}

		return false;
	}

	public function getSize()
	{
		return $this->size;
	}

	/**
	 * @param int $elementsSlice Definition length, e.g. -1 would exclude last element.
	 *
	 * @return string
	 */
	public function getDefinition($elementsSlice = 0)
	{
		if (!isset($this->definition[$elementsSlice]))
		{
			$this->definition[$elementsSlice] = join('.',
				$elementsSlice == 0
					? $this->getDefinitionParts()
					: array_slice($this->getDefinitionParts(), 0, $elementsSlice)
			);
		}

		return $this->definition[$elementsSlice];
	}

	/**
	 * @return array
	 */
	public function getDefinitionParts()
	{
		if (is_null($this->definitionParts))
		{
			$this->definitionParts = static::getDefinitionPartsByChain($this);
		}

		return $this->definitionParts;
	}

	public function getAlias()
	{
		if ($this->custom_alias !== null)
		{
			return $this->custom_alias;
		}

		if ($this->alias === null)
		{
			$this->alias = self::getAliasByChain($this);
		}

		return $this->alias;
	}

	public function setCustomAlias($alias)
	{
		$this->custom_alias = $alias;
	}

	/**
	 * @param Entity $init_entity
	 * @param      $definition
	 *
	 * @return Chain
	 * @throws Main\ArgumentException
	 * @throws SystemException
	 */
	public static function getChainByDefinition(Entity $init_entity, $definition)
	{
		if (!is_string($definition))
		{
			throw new Main\ArgumentException('String expected, but `'.gettype($definition).'` is given.');
		}

		$chain = new Chain;
		$chain->addElement(new ChainElement($init_entity));

		$def_elements = explode('.', $definition);
		$def_elements_size = count($def_elements);

		$prev_entity  = $init_entity;

		$i = 0;

		foreach ($def_elements as &$def_element)
		{
			$is_last_elem  = (++$i == $def_elements_size);

			$not_found = false;

			// all elements should be a Reference field or Entity
			// normal (scalar) field can only be the last element

			if ($prev_entity->hasField($def_element))
			{
				// field has been found at current entity
				$field = $prev_entity->getField($def_element);

				if ($field instanceof Reference)
				{
					$prev_entity = $field->getRefEntity();
				}
				elseif ($field instanceof ExpressionField)
				{
					// expr can be in the middle too
				}
				elseif ($field instanceof OneToMany)
				{
					$prev_entity = $field->getRefEntity();
				}
				elseif ($field instanceof ManyToMany)
				{
					$prev_entity = $field->getRefEntity();
				}
				elseif (!$is_last_elem)
				{
					throw new SystemException(sprintf(
						'Normal fields can be only the last in chain, `%s` %s is not the last.',
						$field->getName(), get_class($field)
					));
				}

				if ($is_last_elem && $field instanceof ExpressionField)
				{
					// we should have own copy of build_from_chains to set join aliases there
					$field = clone $field;
				}

				$chain->addElement(new ChainElement($field));
			}
			elseif (Entity::isExists($def_element)
				&& Entity::getInstance($def_element)->getReferencesCountTo($prev_entity->getName()) == 1
			)
			{
				// def_element is another entity with only 1 reference to current entity
				// need to identify Reference field
				$ref_entity = Entity::getInstance($def_element);
				$field = end($ref_entity->getReferencesTo($prev_entity->getName()));

				$prev_entity = $ref_entity;

				$chain->addElement(new ChainElement(
					array($ref_entity, $field)
				));
			}
			elseif ( ($pos_wh = strpos($def_element, ':')) > 0 )
			{
				$ref_entity_name = substr($def_element, 0, $pos_wh);

				if (strpos($ref_entity_name, '\\') === false)
				{
					// if reference has no namespace, then it'is in the namespace of previous entity
					$ref_entity_name = $prev_entity->getNamespace().$ref_entity_name;
				}

				if (
					Entity::isExists($ref_entity_name)
					&& Entity::getInstance($ref_entity_name)->hasField($ref_field_name = substr($def_element, $pos_wh + 1))
					&& Entity::getInstance($ref_entity_name)->getField($ref_field_name) instanceof Reference
				)
				{
					/** @var Reference $reference */
					$reference = Entity::getInstance($ref_entity_name)->getField($ref_field_name);

					if (
						$reference->getRefEntity()->getFullName() == $prev_entity->getFullName() ||
						is_subclass_of(
							$prev_entity->getDataClass(),
							$reference->getRefEntity()->getDataClass()
						)
					)
					{
						// chain element is another entity with >1 references to current entity
						// def like NewsArticle:AUTHOR, NewsArticle:LAST_COMMENTER
						// NewsArticle - entity, AUTHOR and LAST_COMMENTER - Reference fields
						$chain->addElement(new ChainElement(array(
							Entity::getInstance($ref_entity_name),
							Entity::getInstance($ref_entity_name)->getField($ref_field_name)
						)));

						$prev_entity = Entity::getInstance($ref_entity_name);
					}
					else
					{
						$not_found = true;
					}
				}
				else
				{
					$not_found = true;
				}

			}
			elseif ($def_element == '*' && $is_last_elem)
			{
				continue;
			}
			else
			{
				// unknown chain
				$not_found = true;
			}

			if ($not_found)
			{
				throw new SystemException(sprintf(
					'Unknown field definition `%s` (%s) for %s Entity.',
					$def_element, $definition, $prev_entity->getFullName()
				), 100);
			}
		}

		return $chain;
	}

	public static function getDefinitionByChain(Chain $chain)
	{
		return join('.', static::getDefinitionPartsByChain($chain));
	}

	public static function getDefinitionPartsByChain(Chain $chain)
	{
		$def = array();

		// add members of chain except of init entity
		/** @var $elements ChainElement[] */
		$elements = array_slice($chain->getAllElements(), 1);

		foreach ($elements  as $element)
		{
			//if ($element->getValue() instanceof ExpressionField && $element !== end($elements))
			{
				// skip non-last expressions
				//continue;
			}

			$def[] = $element->getDefinitionFragment();
		}

		return $def;
	}

	public static function appendDefinition($currentDefinition, $newDefinitionPart)
	{
		if ($currentDefinition !== '')
		{
			$currentDefinition .= '.';
		}

		return $currentDefinition.$newDefinitionPart;
	}

	public static function getAliasByChain(Chain $chain)
	{
		$alias = array();

		$elements = $chain->getAllElements();

		// add prefix of init entity
		if (count($elements) > 2)
		{
			$alias[] = $chain->getFirstElement()->getAliasFragment();
		}

		// add other members of chain
		/** @var ChainElement[] $elements */
		$elements = array_slice($elements, 1);

		foreach ($elements  as $element)
		{
			$fragment = $element->getAliasFragment();

			if($fragment <> '')
			{
				$alias[] = $fragment;
			}
		}

		return join('_', $alias);
	}

	/**
	 * @param Entity $entity
	 * @param      $definition
	 *
	 * @return string
	 * @throws Main\ArgumentException
	 * @throws SystemException
	 */
	public static function getAliasByDefinition(Entity $entity, $definition)
	{
		return self::getChainByDefinition($entity, $definition)->getAlias();
	}

	/**
	 * @return bool
	 * @throws SystemException
	 */
	public function hasAggregation()
	{
		$elements = array_reverse($this->chain);

		foreach ($elements as $element)
		{
			/** @var $element ChainElement */
			if ($element->getValue() instanceof ExpressionField && $element->getValue()->isAggregated())
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * @return bool
	 * @throws SystemException
	 */
	public function hasSubquery()
	{
		$elements = array_reverse($this->chain);

		foreach ($elements as $element)
		{
			/** @var $element ChainElement */
			if ($element->getValue() instanceof ExpressionField && $element->getValue()->hasSubquery())
			{
				return true;
			}
		}

		return false;
	}

	public function isConstant()
	{
		return ($this->getLastElement()->getValue() instanceof ExpressionField
			&& $this->getLastElement()->getValue()->isConstant());
	}

	public function forceDataDoublingOff()
	{
		$this->forcesDataDoublingOff = true;
	}

	public function forcesDataDoublingOff()
	{
		return $this->forcesDataDoublingOff;
	}

	/**
	 * @param bool $with_alias
	 *
	 * @return mixed|string
	 * @throws SystemException
	 */
	public function getSqlDefinition($with_alias = false)
	{
		$sql_def = $this->getLastElement()->getSqlDefinition();

		if ($with_alias)
		{
			$helper = $this->getLastElement()->getValue()->getEntity()->getConnection()->getSqlHelper();
			$sql_def .= ' AS ' . $helper->quote($this->getAlias());
		}

		return $sql_def;
	}

	public function __clone()
	{
		$this->custom_alias = null;
	}

	public function dump()
	{
		echo '  '.'   forcesDataDoublingOff: '.($this->forcesDataDoublingOff()?'true':'false');
		echo PHP_EOL;

		$i = 0;
		foreach ($this->chain as $elem)
		{
			echo '  '.++$i.'. ';
			$elem->dump();
			echo PHP_EOL;
		}
	}
}