瀏覽代碼

XML Modelization of the relations, with full support of the previous way (by implementing a method GetRelationQueries). Still, the standard data model has not been migrated to the new format.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@3519 a333f486-631f-4898-b8df-5754b55c2be0
romainq 10 年之前
父節點
當前提交
f2a3b8237f
共有 5 個文件被更改,包括 198 次插入41 次删除
  1. 1 1
      application/displayblock.class.inc.php
  2. 43 18
      core/dbobject.class.php
  3. 59 13
      core/metamodel.class.php
  4. 13 8
      pages/schema.php
  5. 82 1
      setup/compiler.class.inc.php

+ 1 - 1
application/displayblock.class.inc.php

@@ -1418,7 +1418,7 @@ class MenuBlock extends DisplayBlock
 					$this->AddMenuSeparator($aActions);
 					foreach($aRelations as $sRelationCode)
 					{
-						$aActions[$sRelationCode] = array ('label' => MetaModel::GetRelationVerbUp($sRelationCode), 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&class=$sClass&id=$id{$sContext}");
+						$aActions[$sRelationCode] = array ('label' => MetaModel::GetRelationLabel($sRelationCode), 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&class=$sClass&id=$id{$sContext}");
 					}
 				}
 				/*

+ 43 - 18
core/dbobject.class.php

@@ -2531,41 +2531,66 @@ abstract class DBObject implements iDisplay
 	}
 
 	// Return an empty set for the parent of all
+	// May be overloaded.
+	// Anyhow, this way of implementing the relations suffers limitations (not handling the redundancy)
+	// and you should consider defining those things in XML.
 	public static function GetRelationQueries($sRelCode)
 	{
 		return array();
 	}
 	
+	// Reserved: do not overload
+	public static function GetRelationQueriesEx($sRelCode)
+	{
+		return array();
+	}
+
 	public function GetRelatedObjects($sRelCode, $iMaxDepth = 99, &$aResults = array())
 	{
 		foreach (MetaModel::EnumRelationQueries(get_class($this), $sRelCode) as $sDummy => $aQueryInfo)
 		{
 			MetaModel::DbgTrace("object=".$this->GetKey().", depth=$iMaxDepth, rel=".$aQueryInfo["sQuery"]);
 			$sQuery = $aQueryInfo["sQuery"];
-			$bPropagate = $aQueryInfo["bPropagate"];
-			$iDistance = $aQueryInfo["iDistance"];
-
-			$iDepth = $bPropagate ? $iMaxDepth - 1 : 0;
+			//$bPropagate = $aQueryInfo["bPropagate"];
+			//$iDepth = $bPropagate ? $iMaxDepth - 1 : 0;
+			$iDepth = $iMaxDepth - 1;
 
-			$oFlt = DBObjectSearch::FromOQL($sQuery);
-			$oObjSet = new DBObjectSet($oFlt, array(), $this->ToArgsForQuery());
-			while ($oObj = $oObjSet->Fetch())
+			// Note: the loop over the result set has been written in an unusual way for error reporting purposes
+			// In the case of a wrong query parameter name, the error occurs on the first call to Fetch,
+			// thus we need to have this first call into the try/catch, but
+			// we do NOT want to nest the try/catch for the error message to be clear
+			try
+			{
+				$oFlt = DBObjectSearch::FromOQL($sQuery);
+				$oObjSet = new DBObjectSet($oFlt, array(), $this->ToArgsForQuery());
+				$oObj = $oObjSet->Fetch();
+			}
+			catch (Exception $e)
+			{
+				$sClassOfDefinition = $aQueryInfo['_legacy_'] ? get_class($this).'(or a parent)::GetRelationQueries()' : $aQueryInfo['sDefinedInClass'];
+				throw new Exception("Wrong query for the relation $sRelCode/$sClassOfDefinition/{$aQueryInfo['sNeighbour']}: ".$e->getMessage());
+			}
+			if ($oObj)
 			{
-				$sRootClass = MetaModel::GetRootClass(get_class($oObj));
-				$sObjKey = $oObj->GetKey();
-				if (array_key_exists($sRootClass, $aResults))
+				do
 				{
-					if (array_key_exists($sObjKey, $aResults[$sRootClass]))
+					$sRootClass = MetaModel::GetRootClass(get_class($oObj));
+					$sObjKey = $oObj->GetKey();
+					if (array_key_exists($sRootClass, $aResults))
 					{
-						continue; // already visited, skip
+						if (array_key_exists($sObjKey, $aResults[$sRootClass]))
+						{
+							continue; // already visited, skip
+						}
+					}
+	
+					$aResults[$sRootClass][$sObjKey] = $oObj;
+					if ($iDepth > 0)
+					{
+						$oObj->GetRelatedObjects($sRelCode, $iDepth, $aResults);
 					}
 				}
-
-				$aResults[$sRootClass][$sObjKey] = $oObj;
-				if ($iDepth > 0)
-				{
-					$oObj->GetRelatedObjects($sRelCode, $iDepth, $aResults);
-				}
+				while ($oObj = $oObjSet->Fetch());
 			}
 		}
 		return $aResults;

+ 59 - 13
core/metamodel.class.php

@@ -1124,31 +1124,77 @@ abstract class MetaModel
 		return $aResult;
 	}
 
-	public static function EnumRelationProperties($sRelCode)
-	{
-		MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos);
-		return self::$m_aRelationInfos[$sRelCode];
-	}
-
 	final static public function GetRelationDescription($sRelCode)
 	{
 		return Dict::S("Relation:$sRelCode/Description");
 	}
 
-	final static public function GetRelationVerbUp($sRelCode)
+	final static public function GetRelationLabel($sRelCode)
 	{
 		return Dict::S("Relation:$sRelCode/VerbUp");
 	}
 
-	final static public function GetRelationVerbDown($sRelCode)
-	{
-		return Dict::S("Relation:$sRelCode/VerbDown");
-	}
-
 	public static function EnumRelationQueries($sClass, $sRelCode)
 	{
 		MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos);
-		return call_user_func_array(array($sClass, 'GetRelationQueries'), array($sRelCode));
+
+		$aNeighbours = call_user_func_array(array($sClass, 'GetRelationQueriesEx'), array($sRelCode));
+
+		// Translate attributes into queries (new style of spec only)
+		foreach($aNeighbours as $trash => &$aNeighbourData)
+		{
+			try
+			{
+				if (strlen($aNeighbourData['sQuery']) == 0)
+				{
+					$oAttDef = self::GetAttributeDef($sClass, $aNeighbourData['sAttribute']);
+					if ($oAttDef instanceof AttributeExternalKey)
+					{
+						$sTargetClass = $oAttDef->GetTargetClass();
+						$aNeighbourData['sQuery'] = 'SELECT '.$sTargetClass.' AS o WHERE o.id = :this->'.$aNeighbourData['sAttribute'];
+					}
+					elseif ($oAttDef instanceof AttributeLinkedSet)
+					{
+						$sLinkedClass = $oAttDef->GetLinkedClass();
+						$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
+						if ($oAttDef->IsIndirect())
+						{
+							$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
+							$oRemoteAttDef = self::GetAttributeDef($sLinkedClass, $sExtKeyToRemote);
+							$sRemoteClass = $oRemoteAttDef->GetTargetClass();
+	
+							$aNeighbourData['sQuery'] = "SELECT $sRemoteClass AS o JOIN $sLinkedClass AS lnk ON lnk.$sExtKeyToRemote = o.id WHERE lnk.$sExtKeyToMe = :this->id";
+						}
+						else
+						{
+							$aNeighbourData['sQuery'] = "SELECT $sLinkedClass AS o WHERE o.$sExtKeyToMe = :this->id";
+						}
+					}
+					else
+					{
+						throw new Exception("Unexpected attribute type for '{$aNeighbourData['sAttribute']}'. Expecting a link set or external key.");
+					}
+				}
+			}
+			catch (Exception $e)
+			{
+				$sClassOfDefinition = $aNeighbourData['_legacy_'] ? $sClass.'(or a parent)::GetRelationQueries()' : $aNeighbourData['sDefinedInClass'];
+				throw new Exception("Wrong definition for the relation $sRelCode/$sClassOfDefinition/{$aNeighbourData['sNeighbour']}: ".$e->getMessage());
+			}
+		}
+
+		// Merge legacy and new specs
+		$aLegacy = call_user_func_array(array($sClass, 'GetRelationQueries'), array($sRelCode));
+		foreach($aLegacy as $sId => $aLegacyEntry)
+		{
+			$aLegacyEntry['_legacy_'] = true;
+			$aNeighbours[] = array(
+				'_legacy_' => true,
+				'sQuery' => $aLegacyEntry['sQuery'],
+				'sNeighbour' => $sId
+			);
+		}
+		return $aNeighbours;
 	}
 
 	//

+ 13 - 8
pages/schema.php

@@ -341,8 +341,7 @@ function DisplayClassesList($oPage, $sContext)
 		$oPage->add("<li>".MakeRelationHLink($sRelCode, $sContext)."\n");
 		$oPage->add("<ul>\n");
 		$oPage->add("<li>Description: ".htmlentities(MetaModel::GetRelationDescription($sRelCode), ENT_QUOTES, 'UTF-8')."</li>\n");
-		$oPage->add("<li>Verb up: ".htmlentities(MetaModel::GetRelationVerbUp($sRelCode), ENT_QUOTES, 'UTF-8')."</li>\n");
-		$oPage->add("<li>Verb down: ".htmlentities(MetaModel::GetRelationVerbDown($sRelCode), ENT_QUOTES, 'UTF-8')."</li>\n");
+		$oPage->add("<li>Label: ".htmlentities(MetaModel::GetRelationLabel($sRelCode), ENT_QUOTES, 'UTF-8')."</li>\n");
 		$oPage->add("</ul>\n");
 		$oPage->add("</li>\n");
 	}
@@ -517,11 +516,9 @@ function DisplayClassDetails($oPage, $sClass, $sContext)
 function DisplayRelationDetails($oPage, $sRelCode, $sContext)
 {
 	$sDesc = MetaModel::GetRelationDescription($sRelCode);
-	$sVerbDown = MetaModel::GetRelationVerbDown($sRelCode);
-	$sVerbUp = MetaModel::GetRelationVerbUp($sRelCode);
+	$sLabel = MetaModel::GetRelationLabel($sRelCode);
 	$oPage->add("<h1>".Dict::Format('UI:Schema:Relation_Code_Description', $sRelCode, $sDesc)."</h1>");
-	$oPage->p(Dict::Format('UI:Schema:RelationDown_Description', $sVerbDown));
-	$oPage->p(Dict::Format('UI:Schema:RelationUp_Description', $sVerbUp));
+	$oPage->p(Dict::Format('UI:Schema:RelationUp_Description', $sLabel));
 
 	$oPage->add("<ul id=\"RelationshipDetails\" class=\"treeview\">\n");
 	foreach(MetaModel::GetClasses() as $sClass)
@@ -533,8 +530,9 @@ function DisplayRelationDetails($oPage, $sRelCode, $sContext)
 			$oPage->add("<ul>\n");
 			foreach ($aRelQueries as $sRelKey => $aQuery)
 			{
-				$sQuery = $aQuery['sQuery'];
-				$iDistance = $aQuery['iDistance'];
+				$sQuery = isset($aQuery['sQuery']) ? $aQuery['sQuery'] : '';
+				$sAttribute = isset($aQuery['sAttribute']) ? $aQuery['sAttribute'] : '';
+				/*
 				if ($aQuery['bPropagate'])
 				{
 					$oPage->add("<li>".Dict::Format('UI:Schema:RelationPropagates', $sRelKey, $iDistance, $sQuery)."</li>\n");
@@ -543,6 +541,13 @@ function DisplayRelationDetails($oPage, $sRelCode, $sContext)
 				{
 					$oPage->add("<li>".Dict::Format('UI:Schema:RelationDoesNotPropagate', $sRelKey, $iDistance, $sQuery)."</li>\n");
 				}
+				*/
+				$sLabel = (strlen($sQuery) > 0) ? $sQuery : $sAttribute;
+				if ($aQuery['_legacy_'])
+				{
+					$sLabel .= ' (<b>Old style specification</b>: it is recommended to upgrade to XML)';
+				}
+				$oPage->add("<li>".$sLabel."</li>\n");
 			}
 			$oPage->add("</ul>\n");
 			$oPage->add("</li>\n");

+ 82 - 1
setup/compiler.class.inc.php

@@ -1332,7 +1332,88 @@ EOF;
 				$sMethods .= "\n\n".$sMethodCode."\n";
 			}		
 		}
-	
+
+		// Relations
+		//
+		$oRelations = $oClass->GetOptionalElement('relations');
+		if ($oRelations)
+		{
+			$aRelations = array();
+			foreach($oRelations->childNodes as $oRelation)
+			{
+				if ($oRelation->tagName != 'relation') continue;
+
+				$sRelationId = $oRelation->getAttribute('id');
+
+				$oNeighbours = $oRelation->GetUniqueElement('neighbours');
+				foreach($oNeighbours->childNodes as $oNeighbour)
+				{
+					if ($oNeighbour->tagName != 'neighbour') continue;
+					$sNeighbourId = $oNeighbour->getAttribute('id');
+
+					if (($oNeighbour->GetChildText('query') == '') && ($oNeighbour->GetChildText('attribute') == ''))
+					{
+						throw new DOMFormatException("Relation '$sRelationId': either a query or an attribute must be specified");
+					}
+					if (($oNeighbour->GetChildText('query') != '') && ($oNeighbour->GetChildText('attribute') != ''))
+					{
+						throw new DOMFormatException("Relation '$sRelationId': both a query and and attribute have been specified... which one should be used?");
+					}
+					$aData = array(
+						'_legacy_' => false,
+						'sDefinedInClass' => $sClass,
+						'sNeighbour' => $sNeighbourId,
+						'sQuery' => $oNeighbour->GetChildText('query'),
+						'sAttribute' => $oNeighbour->GetChildText('attribute'),
+					);
+
+					$oReverse = $oNeighbour->GetOptionalElement('reverse');
+					if ($oReverse)
+					{
+						$aData['sReverseClass'] = $oReverse->GetChildText('source_class');
+						$aData['sReverseRelation'] = $oReverse->GetChildText('relation');
+						$aData['sReverseNeighbour'] = $oReverse->GetChildText('neighbour');
+					}
+
+					$oRedundancy = $oNeighbour->GetOptionalElement('redundancy');
+					if ($oRedundancy)
+					{
+						$oEnabled = $oRedundancy->GetUniqueElement('enabled');
+						$aData['bEnabledValue'] = ($oEnabled->GetChildText('value', 'false') == 'true');
+						$aData['sEnabledMode'] = $oEnabled->GetChildText('mode', 'fixed');
+						$oThreshold = $oRedundancy->GetUniqueElement('threshold');
+						$aData['iThresholdValue'] = (int) $oThreshold->GetChildText('value', 1);
+						$aData['sThresholdMode'] = $oThreshold->GetChildText('mode', 'fixed');
+					}
+
+					$aRelations[$sRelationId][$sNeighbourId] = $aData;
+				}
+			}
+
+			$sMethods .= "\tpublic static function GetRelationQueriesEx(\$sRelCode)\n";
+			$sMethods .= "\t{\n";
+			$sMethods .= "\t\tswitch (\$sRelCode)\n";
+			$sMethods .= "\t\t{\n";
+			foreach ($aRelations as $sRelationId => $aRelationData)
+			{
+				$sMethods .= "\t\tcase '$sRelationId':\n";
+				$sMethods .= "\t\t\t\$aRels = array(\n";
+				foreach ($aRelationData as $sNeighbourId => $aData)
+				{
+					//$sData = str_replace("\n", "\n\t\t\t\t", var_export($aData, true));
+					$sData = var_export($aData, true);
+					$sMethods .= "\t\t\t\t'$sNeighbourId' => $sData,\n";
+				}
+				$sMethods .= "\t\t\t);\n";
+				$sMethods .= "\t\t\treturn array_merge(\$aRels, parent::GetRelationQueriesEx(\$sRelCode));\n\n";
+			}
+			$sMethods .= "\t\tdefault:\n";
+			$sMethods .= "\t\t\treturn parent::GetRelationQueries(\$sRelCode);\n";
+			$sMethods .= "\t\t}\n";
+			$sMethods .= "\t}\n";
+		}
+
+
 		// Let's make the whole class declaration
 		//
 		$sPHP = "\n\n$sCodeComment\n";