/** * Constructor * @param string $name name of the corresponding class * @param string $type either "has" or "composed_of" * @param string $class the name of the class related * @param epClassMap */ public function __construct($name, $type, $class, $is_many = false, $inverse = false, $class_map = false) { parent::__construct($name, $type, $class_map); $this->setIsMany($is_many); $this->setClass($class); $this->setInverse($inverse); }
/** * Validate related class and inverse on a field map * @param epFieldMap $fm the field map to be checked * @param string $class the name of the class that the field belongs to * @return array (errors) */ protected function _validateRelationshipField(&$fm, $class) { // array to hold error messages $errors = array(); // // 1. check the opposite class for the field // // string for class and field $class_field = '[' . $class . '::' . $fm->getName() . ']'; // does the relation field have the related class defined? if (!($rclass = $fm->getClass())) { // shouldn't happend $errors[] = $class_field . ' does not have opposite class specified'; return $errors; } // does the related class exist? if (!isset($this->class_maps[$rclass])) { // alert if not $errors[] = 'Class [' . $rclass . '] for ' . $class_field . ' does not exist'; return $errors; } // // 2. check inverse of the field // // does this field have an inverse? if (!($inverse = $fm->getInverse())) { return $errors; } // get the related class map $rcm = $this->class_maps[$rclass]; // get all fields point to the current class in the related class $rfields = $rcm->getFieldsOfClass($class); // 2.a. default inverse (that is, set to true) if (true === $inverse) { // the related class must have only one relationship var to the current class if (!$rfields) { $errors[] = 'No inverse found for ' . $class_field; } else { if (count($rfields) > 1) { $errors[] = 'Ambiguilty in the inverse of ' . $class_field; } else { $rfms = array_values($rfields); $fm->setInverse($rfms[0]->getName()); $rfms[0]->setInverse($fm->getName()); } } return $errors; } // 2.b. inverse is specified // check if inverse exists if (!isset($rfields[$fm->getClass() . ':' . $inverse]) || !$rfields[$fm->getClass() . ':' . $inverse]) { $errors[] = 'Inverse of ' . $class_field . ' (' . $fm->getClass() . '::' . $inverse . ') does not exist'; return $errors; } // get the field map for the inverse $rfm = $rfields[$fm->getClass() . ':' . $inverse]; // set up the inverse on the other side -only if- inverse on the other side // is -not- already set or set to default if (!($rinverse = $rfm->getInverse()) || $rinverse === true) { $rfm->setInverse($fm->getName()); return $errors; } // if specified, check duality if ($class != $rfm->getClass() || $rinverse != $fm->getName()) { $errors[] = 'Inverse of [' . $rcm->getName() . '::' . $fm->getName() . '] is not specified as ' . $class_field; } return $errors; }
/** * Returns the relation ids for the variable specified of the object * @param epObject $o the object * @param epFieldMap $fm the field map of the variable * @param epClassMap $cm the class map of the object * @return false|string|array */ public function getRelationIds(&$o, $fm, $cm) { // make sure we are dealing with valid object and non-primitive field if (!$o || !$fm || !$cm) { return false; } // object needs to have a valid id and has to be non-primitive if (!($oid_a = $o->epGetObjectId())) { return false; } // get class_a, var_a, and the related class $base_a = $fm->getBase_a(); $class_a = $cm->getName(); $var_a = $fm->getName(); $base_b = $fm->getBase_b(); if (!$base_a || !$class_a || !$var_a || !$base_b) { throw new epExceptionManager('Cannot find related class for var [' . $class_a . '::' . $var_a . ']'); return false; } // switch relations table $this->_setRelationTable($base_a, $base_b); // make an example relation objects if (!($eo =& $this->_relationExample($class_a, $oid_a, $var_a, $base_b))) { return false; } // find all relation objects using the example object // (find from db only, false: no cache, false: don't convert to objects) $rs =& parent::find($eo, EP_GET_FROM_DB, false, false); // convert result into oids $oids_b = null; if ($fm->isSingle()) { if (is_array($rs) && count($rs) > 1) { throw new epExceptionManager('Field ' . $fm->getName() . ' mapped as composed_of_/has_one but is associated with > 1 objects'); return false; } if ($rs) { $oids_b = $rs[0]; } } else { if ($fm->isMany()) { $oids_b = array(); if ($rs) { $oids_b = $rs; } } } return $oids_b; }
/** * Generates SQL statement for -this- node and set up * aliases for all classes (root class and subclasses) * @return false|string * @throws epExceptionQueryPath */ protected function _generateSql() { // -no- sql for primitive field if ($this->fm->isPrimitive()) { // only pass aliases from the parent to children $this->class2alias = $this->getParent()->getAliases(); return ''; } // get base a $base_a = $this->fm->getBase_a(); // get base_b $base_b = $this->fm->getBase_b(); // get class alias from parent if (!($class2alias_a = $this->getParent()->getAliases())) { throw new epExceptionQueryPath("no aliases found from parent node"); return false; } // check if spcific class is set. use it only if so. if ($sc = $this->specificClass()) { // get class map of specific class if (!($cm_b = $this->_em()->getClassMap($sc))) { throw new epExceptionQueryPath("no class map for classes and '{$sc}'"); return false; } } else { // get class map of base b if (!($cm_b = $this->_em()->getClassMap($base_b))) { throw new epExceptionQueryPath("no class map for classes and '{$base_b}'"); return false; } } // get relationship table with base_a and base_b if (!($rt = $this->_em()->getRelationTable($base_a, $base_b))) { throw new epExceptionQueryPath("no relationship table for classes '{$base_a}' and '{$base_b}'"); return false; } $aliases = array(); $aliases[] = ''; // check if we have any children contained $children_contained = false; if ($children = $this->getChildren()) { foreach ($children as $child) { if ($child->isRoot() && $child->isContained()) { $aliases[] = $child->getAlias(); $children_contained = true; } } } $sql = ''; // go through each contained child foreach ($aliases as $alias) { // get alias for relationship table if (!isset($this->table2alias[$rt . '.' . $alias])) { $rt_alias = $this->_am()->getTableAlias($rt, true); $this->table2alias[$rt . '.' . $alias] = $rt_alias; } else { $rt_alias = $this->table2alias[$rt . '.' . $alias]; } // generate sql for class_a (including its subclasses) if ($alias || !$children_contained) { $sql .= $this->_generateSqlClassA($rt, $rt_alias, $this->fm->getName(), $class2alias_a); } // assemble sql for class b and subclasses $cms_b = array(); // collect all children if no specific class set if (!$this->specificClass()) { $cms_b = $cm_b->getChildren(true); } array_unshift($cms_b, $cm_b); foreach ($cms_b as $cm_b_) { // skip abstract if ($cm_b_->isAbstract()) { continue; } // get class b name and make alias $class_b = $cm_b_->getName(); if ($children_contained) { // @@@ alias key may be in the form of 'ClassName.alias' // @@@ to differentiate multiple aliases of the same class if (!isset($this->class2alias[$class_b . '.' . $alias])) { if (!$alias) { $alias_b = $this->_am()->getClassAlias($class_b, true); } else { $alias_b = $this->class2alias[$class_b . '.']; $this->_am()->setClassAlias($class_b, $alias_b . $alias); } $this->class2alias[$class_b . '.' . $alias] = $alias_b . $alias; } $alias_b = $this->class2alias[$class_b . '.' . $alias]; } else { if (!isset($this->class2alias[$class_b])) { $alias_b = $this->_am()->getClassAlias($class_b, true); $this->class2alias[$class_b] = $alias_b; } else { $alias_b = $this->class2alias[$class_b]; } } // generate sql for class b if ($alias || !$children_contained) { $sql .= $this->_generateSqlClassB($rt_alias, $cm_b_->getTable(), $base_b, $class_b, $alias_b, $cm_b_->getOidColumn()); } } } return $sql; }
/** * Test var orm tag */ function testVarPrimTag() { // get all supported column types include_once EP_SRC_ORM . '/epFieldMap.php'; $alltypes = epFieldMap::getSupportedTypes(); $keytypes = array('', 'unique', 'index'); $keynames = array('', 'testingname'); foreach ($alltypes as $type) { // skip relationship types if ($type == epFieldMap::DT_HAS || $type == epFieldMap::DT_COMPOSED_OF) { continue; } $name = '_' . md5($type); $params = array(); if ($type == epFieldMap::DT_CHAR || $type == epFieldMap::DT_TEXT || $type == epFieldMap::DT_BLOB) { $params[] = rand(1, self::MAX_REPEATS); } elseif ($type == epFieldMap::DT_DECIMAL) { $params[] = rand(1, self::MAX_REPEATS); $params[] = rand(1, self::MAX_REPEATS); } foreach ($keytypes as $keytype) { foreach ($keynames as $keyname) { // skip the situations where we have no keytype but we have a keyname if ($keytype == '' && $keyname != '') { continue; } $this->_varPrimTag($name, $type, $params, $keytype, $keyname); } } } }
/** * Updates the value of the inverse var * @param epFieldMap $fm The field map toward the inverse var * @param epObject &$o The opposite object * @param string $actoin The update action to take: INVERSE_ADD|REMOVE * @param bool $one_way Whether inverse update is one way only * @return bool */ protected function _updateInverse($fm, &$o, $action = epObject::INVERSE_ADD, $one_way = true) { // check if object is epObject if (!$o || !$o instanceof epObject) { return false; } // no action if an object is updating (to prevent endless loop) if ($o->epIsUpdating($action)) { return true; } // get inverse var if (!($ivar = $fm->getInverse())) { return true; } // set inverse updating flag if ($one_way) { $this->epSetUpdating(true, $action); } $o->epSetUpdating(true, $action); // a single-valued field if (!$o->epIsMany($ivar)) { switch ($action) { case epObject::INVERSE_ADD: $o[$ivar] = $this; break; case epObject::INVERSE_REMOVE: $o[$ivar] = null; break; } } else { switch ($action) { case epObject::INVERSE_ADD: $o[$ivar][] = $this; break; case epObject::INVERSE_REMOVE: $o[$ivar]->remove($this); break; } } // reset inverse updating flag $o->epSetUpdating(false, $action); if ($one_way) { $this->epSetUpdating(false, $action); } return true; }
/** * Make where part of a SQL select for relationship fields * @param epDbObject $db the db connection * @param epFieldMap $fm the field map * @param epClassMap $cm the child object for query * @param string $alias the alias of this table in the previous part * @return array('from', 'where') * @author Oak Nauhygon <*****@*****.**> * @author Trevan Richins <*****@*****.**> */ public static function sqlSelectRelations($db, $fm, $cm, $table, $alias, $parentTable) { $base_a = $fm->getBase_a(); $class_a = $cm->getName(); $var_a = $fm->getName(); $base_b = $fm->getBase_b(); // call manager to get relation table for base class a and b $rt = epManager::instance()->getRelationTable($base_a, $base_b); // the alias of the table we are dealing with right now $tbAlias = '_' . $alias; $rtAlias = 'rt' . $alias; // quoted aliases (avoid repeating) $tbAlias_q = $db->quoteId($tbAlias); $rtAlias_q = $db->quoteId($rtAlias); // compute 'from' parts: tables with aliases $from = array(); $from[] = $db->quoteId($table) . ' AS ' . $tbAlias_q; $from[] = $db->quoteId($rt) . ' AS ' . $rtAlias_q; // compute expressions 'where' $where = array(); // rt.class_a = $where[] = $rtAlias_q . '.' . $db->quoteId('class_a') . ' = ' . $db->quote($class_a); // rt.var_a = $where[] = $rtAlias_q . '.' . $db->quoteId('var_a') . ' = ' . $db->quote($var_a); // rt.base_b = $where[] = $rtAlias_q . '.' . $db->quoteId('base_b') . ' = ' . $db->quote($base_b); // rt.class_b = TODO: doesn't look like it is used //$where .= 'rt.'.$db->quoteId('class_b') . ' = ' . $db->quote($val->getClass()); // A.oid = rt.oid_a $where[] = $db->quoteId($parentTable) . '.' . $db->quoteId($cm->getOidColumn()) . ' = ' . $rtAlias_q . '.' . $db->quoteId('oid_a'); // Child.oid = rt.oid_b $where[] = $tbAlias_q . '.' . $db->quoteId($fm->getClassMap()->getOidColumn()) . ' = ' . $rtAlias_q . '.' . $db->quoteId('oid_b'); return array('from' => $from, 'where' => $where); }
/** * Toggles the token type of data types between EPL_T_DATA_TYPE * and EPL_T_IDENTIFIER. This is to allow data types to be * used for class names. */ public function toggleDataTypeTokens() { // get data types (including 'has' and 'composed_of') $dtypes = epFieldMap::getSupportedTypes(); // go through all keywords foreach ($this->keywords as $keyword => $token) { if (!in_array($keyword, $dtypes)) { continue; } // has if ($keyword == epFieldMap::DT_HAS) { $this->keywords[$keyword] = $token == EPL_T_HAS ? EPL_T_IDENTIFIER : EPL_T_HAS; continue; } // composed of if ($keyword == epFieldMap::DT_COMPOSED_OF) { $this->keywords[$keyword] = $token == EPL_T_COMPOSED_OF ? EPL_T_IDENTIFIER : EPL_T_COMPOSED_OF; continue; } // primitive types $this->keywords[$keyword] = $token == EPL_T_DATA_TYPE ? EPL_T_IDENTIFIER : EPL_T_DATA_TYPE; } }
/** * Check if the value to be set matches the type set in orm tag * @param epObject|string $value * @param epFieldMap $fm * @return boolean */ protected function _checkValueType($value, $fm) { // no check if no manager if (!$this->ep_m) { return true; } // no checking on primitve, ignore false|null|empty, and non-epObject if ($fm->isPrimitive() || !$value) { // always true return true; } // epObject if ($value instanceof epObject) { return $this->_typeMatch($this->ep_m->getClass($value), $fm->getClass()); } else { if (is_array($value) || $value instanceof epArray) { foreach ($value as $k => $v) { if (!$v instanceof epObject) { continue; } if (!$this->_typeMatch($this->ep_m->getClass($v), $fm->getClass())) { return false; } } return true; } } return true; }
/** * Builds SQL statement from 'contains' or alias assignment node * @param epQueryNode &$node either a 'contains' node or an assignment node * @param epFieldMap $fm the starting field map * @param array $var_exprs expressions for var * @param array|epObject $arg the argument|value for the operation * @return false|string * @throws epExceptionQueryBuilder */ protected function _buildSqlRelationship(epQueryNode &$node, $fm, $var_exprs, $arg) { // is arg a string if (is_string($arg)) { // go through each where (notice &) foreach ($var_exprs as $alias => &$expr) { // remove last alias $expr = str_replace($alias, $arg, $expr); // remove this alias (important) unset($this->aliases[$alias]); } return implode(' OR ', $var_exprs); } else { if (is_array($arg) || $arg instanceof epObject) { // convert array to an object $o = $arg; if (is_array($arg)) { // false: no event dispatching, true: null vars if no value $o =& $this->em->createFromArray($fm->getClass(), $arg, false, true); // object and children not to be committed $o->epSetCommittable(false, true); } // array to keep all expressions $exprs = array(); // go through each var exprs foreach ($var_exprs as $var_alias => $var_expr) { // build sql for array or object $obj_exprs = $this->_buildSqlObject($o, $fm, $var_alias); // go through each returned expr from the array or object foreach ($obj_exprs as $obj_expr) { $exprs[] = $obj_expr . ' AND ' . $var_expr; } } return implode(' OR ', $exprs); } } // unrecognized throw new epExceptionQueryBuilder($this->_e("Unrecognized argument in 'contains()'", $node)); return false; }
/** * Constructor * @param string tag value */ public function __construct($value) { // make match pattern of all supported types if (!self::$alltypes) { include_once EP_SRC_ORM . '/epFieldMap.php'; self::$alltypes = epFieldMap::getSupportedTypes(); } parent::__construct($value); }