oOQLClassNode = $oOQLClassNode; $this->oBuild = $oBuild; } /** * Assign attributes on their original classes * * @param string $sIncomingKeyAttCode Key used for the join (entry point of the class) * * @return \OQLClassNode * @throws \CoreException */ public function Resolve($sIncomingKeyAttCode = null) { $sClass = $this->oOQLClassNode->GetNodeClass(); $sClassAlias = $this->oOQLClassNode->GetNodeClassAlias(); $aExpectedAttributes = $this->oBuild->m_oQBExpressions->GetUnresolvedFields($sClassAlias); if (!is_null($sIncomingKeyAttCode) && !isset($aExpectedAttributes[$sIncomingKeyAttCode])) { // Add entry point as expected attribute $aExpectedAttributes[$sIncomingKeyAttCode] = new FieldExpression($sIncomingKeyAttCode, $sClassAlias); } $aClasses = MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, false); /** @var OQLClassNode[] $aClassAndAncestorsNodes */ $aClassAndAncestorsNodes = array(); foreach ($aClasses as $sFamilyClass) { // Remove unnecessary classes if (MetaModel::HasTable($sFamilyClass)) { $aClassAndAncestorsNodes[$sFamilyClass] = null; } } if (empty($aClassAndAncestorsNodes)) { throw new CoreException("Impossible to query the class $sClass"); } $oBaseNode = null; $aTranslateFields = array(); foreach ($aExpectedAttributes as $sAttCode => $oExpression) { // 'id' is managed later if ($sAttCode == 'id') { continue; } // Attributes can be stored in attributes list or for magic ones into filter codes list. $sOriginClass = null; if (MetaModel::IsValidAttCode($sClass, $sAttCode)) { $sOriginClass = MetaModel::GetAttributeOrigin($sClass, $sAttCode); } else if ($sAttCode == 'id') { $sOriginClass = $sClass; } else { continue; } if (!isset($aClassAndAncestorsNodes[$sOriginClass]) || is_null($aClassAndAncestorsNodes[$sOriginClass])) { if ($sOriginClass == $sClass) { $sOriginClassAlias = $sClassAlias; } else { $sOriginClassAlias = $this->oBuild->GenerateTableAlias($sClassAlias.'_'.$sOriginClass, $sClass); } $oOriginClassNode = new OQLClassNode($this->oBuild, $sOriginClass, $sOriginClassAlias, $sClassAlias); $aClassAndAncestorsNodes[$sOriginClass] = $oOriginClassNode; } else { $oOriginClassNode = $aClassAndAncestorsNodes[$sOriginClass]; } if ($sOriginClass != $sClass) { // Alias changed, set a new translation $sOriginClassAlias = $oOriginClassNode->GetNodeClassAlias(); $aTranslateFields[$sClassAlias][$sAttCode] = new FieldExpression($sAttCode, $sOriginClassAlias); } // Add Joins corresponding to external keys $this->ResolveJoins($sAttCode, $oOriginClassNode); if ($sAttCode === $sIncomingKeyAttCode) { // This is the entry point of the class $oBaseNode = $oOriginClassNode; } } // Create joins for ancestor classes /** @var \OQLClassNode $oBaseNode */ $sFirstValidAncestor = null; foreach ($aClassAndAncestorsNodes as $sOriginClass => $oOriginClassNode) { if (is_null($sFirstValidAncestor)) { $sFirstValidAncestor = $sOriginClass; } if (is_null($oOriginClassNode)) { continue; } if (is_null($oBaseNode)) { $oBaseNode = $oOriginClassNode; continue; } if ($oBaseNode === $oOriginClassNode) { // Don't link to itself continue; } $oBaseNode->AddInnerJoin($oOriginClassNode, 'id', 'id'); } if (is_null($oBaseNode)) { // If no class was generated above, keep the first valid ancestor if (is_null($sFirstValidAncestor) || ($sFirstValidAncestor == $sClass)) { // take current node $oBaseNode = $this->oOQLClassNode->CloneNode(); } else { // Use the first valid class to build a default node $sDefaultClassAlias = $this->oBuild->GenerateTableAlias($sClassAlias.'_'.$sFirstValidAncestor, $sClass); $oBaseNode = new OQLClassNode($this->oBuild, $sFirstValidAncestor, $sDefaultClassAlias); } } if (isset($aExpectedAttributes['id']) && !isset($aClassAndAncestorsNodes[$sClass])) { $sFirstClassAlias = $oBaseNode->GetNodeClassAlias(); $aTranslateFields[$sClassAlias]['id'] = new FieldExpression('id', $sFirstClassAlias); } $this->oBuild->m_oQBExpressions->Translate($aTranslateFields, false); // Add Joins corresponding to 'id' $this->ResolveJoins('id', $oBaseNode); // Add finalclass condition if not the requested class if ($oBaseNode->GetNodeClass() != $sClass) { $sExpectedClasses = implode("', '", MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL)); $oInExpression = Expression::FromOQL("`".$oBaseNode->GetNodeClassAlias()."`.finalclass IN ('$sExpectedClasses')"); $oTrueExpression = new TrueExpression(); $aCoalesceAttr = array($oInExpression, $oTrueExpression); $oFinalClassRestriction = new FunctionExpression("COALESCE", $aCoalesceAttr); $this->oBuild->m_oQBExpressions->AddCondition($oFinalClassRestriction); } return $oBaseNode; } /** * Move the joins from the selected class to the class where the external key is instantiated * The joined class is also resolved using the right key as entry point * * @param string $sAttCode (can be an external key) * @param \OQLClassNode $oOriginClassNode real class to join * * @throws \CoreException */ private function ResolveJoins($sAttCode, OQLClassNode $oOriginClassNode) { // Joins on the selected class $aJoins = $this->oOQLClassNode->GetJoins(); if (isset($aJoins[$sAttCode])) { foreach ($aJoins[$sAttCode] as $oBaseOQLJoin) { // transfer the join from OQL class tree to actual class tree $oBaseJoinedClassNode = $oBaseOQLJoin->GetOOQLClassNode(); $oOQLActualClassTreeResolver = new OQLActualClassTreeResolver($oBaseJoinedClassNode, $this->oBuild); // Use the right key to link to actual join class tree $oResolvedClassNode = $oOQLActualClassTreeResolver->Resolve($oBaseOQLJoin->GetRightField()); $oOriginClassNode->AddOQLJoin($sAttCode, $oBaseOQLJoin->NewOQLJoinWithClassNode($oResolvedClassNode)); } } } }