查询数据转换为模型对象
@access public
@param  array $result     查询数据
@param  array $options    查询参数
@param  bool  $resultSet  是否为数据集查询
@param  array $withRelationAttr  关联字段获取器
@return void
182186
protected function resultToModel(&$result, $options = [], $resultSet = false, $withRelationAttr = [])
    {
        // 动态获取器
        if (!empty($options['with_attr']) && empty($withRelationAttr)) {
            foreach ($options['with_attr'] as $name => $val) {
                if (strpos($name, '.')) {
                    list($relation, $field) = explode('.', $name);

                    $withRelationAttr[$relation][$field] = $val;
                    unset($options['with_attr'][$name]);
                }
            }
        }

        $condition = (!$resultSet && isset($options['where']['$and'])) ? $options['where']['$and'] : null;
        $result    = $this->model->newInstance($result, $condition);

        // 动态获取器
        if (!empty($options['with_attr'])) {
            $result->setModelAttrs($options['with_attr']);
        }

        // 关联查询
        if (!empty($options['relation'])) {
            $result->relationQuery($options['relation'], $withRelationAttr);
        }

        // 预载入查询
        if (!$resultSet && !empty($options['with'])) {
            $result->eagerlyResult($result, $options['with'], $withRelationAttr);
        }

        // 关联统计
        if (!empty($options['with_count'])) {
            foreach ($options['with_count'] as $val) {
                $result->relationCount($result, $val[0], $val[1], $val[2]);
            }
        }

    }
848778
protected function processRelationQuery($relation, $name, $value)
    {
        if (!$this->hasRelation($relation)) {
            return;
        }

        $this->reqJoin($relation);

        list($name, $op) = $this->parseNameAndOp($name);

        /** @var BaseModelV2 $related */
        $related = $this->$relation();
        if (!$related->hasColumn($name)) {
            return;
        }

        $this->queryByOp($related->getTable() . '.' . $name, $op, $value);
    }
131317
private function mapGroup($linkTableName, $relation, $fields = '*', $where = [], $orderBy = ''): Result
    {
        // 生成关联表模型对象
        $table = $this->makeTable($linkTableName);

        // 生成本表关联字段与关联表关联字段
        list ($field, $linkField) = $this->relation($relation);

        // 生成本表关联字段的所有值
        $keys = $this->getMapKeys($field);
        if (!$keys) {
            return $this;
        }

        $linkResult = [];
        foreach ($keys as $value) {
            $where[$linkField] = $value;
            $linkData = $table->groupBy($linkField)->select($this->append($fields, $linkField), $where, $orderBy)
                ->toArray();

            //如果没有关联数据
            if (!$linkData) {
                continue;
            }

            $key = $linkData[0][$linkField];
            if (!array_key_exists($key, $linkResult)) {
                $linkResult[$key] = [];
            }
            $linkResult[$key] = $linkData[0];
        }

        $fields = $this->table->createFields($fields);

        // 为本表每行数据补充字段
        foreach ($this->data as $key => $row) {
            // 字段补充成功
            if ($this->mapFill($linkResult, $field, $row, $key)) {
                continue;
            }

            // 没有对应成功,补充空字符串
            $this->data[$key] = $this->appendNull($row, $fields);
        }
        $this->row2Obj();
        return $this;
    }
516916
protected function _getRelatedRowset(
        Garp_Model $model, Garp_Db_Table_Row $row, Garp_Util_Configuration $options
    ) {
        /**
         * An optional passed SELECT object will be passed by reference after every query.
         * This results in an error when 'clone' is not used, because correlation names will be
         * used twice (since they were set during the first iteration). Using 'clone' makes sure
         * a brand new SELECT object is used every time that hasn't been soiled by a possible
         * previous query.
         */
        $conditions = is_null($options['conditions']) ? null : clone $options['conditions'];
        $otherModel = $options['modelClass'];
        if (!$otherModel instanceof Zend_Db_Table_Abstract) {
            $otherModel = new $otherModel();
        }
        // Bound models should respect the CMS context settings of the root model
        $otherModel->setCmsContext($model->isCmsContext());
        /**
         * Do not cache related queries. The "outside" query should be the only
         * query that's cached.
         */
        $originalCacheQueriesFlag = $otherModel->getCacheQueries();
        $otherModel->setCacheQueries(false);
        $modelName = get_class($otherModel);
        $relatedRowset = null;

        // many to many
        if (!empty($options['bindingModel'])) {
            $relatedRowset = $row->findManyToManyRowset(
                $otherModel,
                $options['bindingModel'],
                $options['rule2'],
                $options['rule'],
                $conditions
            );
        } else {
            /**
             * 'mode' is used to clear ambiguity with homophilic relationships. For example,
             * a Model_Doc can have have child Docs and one parent Doc.
             * The conditionals below can never tell which method to call
             * (findParentRow or findDependentRowset) from the referenceMap.
             *
             * Therefore, we can help the decision-making by passing "mode". This can either be
             * "parent" or "dependent", which will then force a call to
             * findParentRow and findDependentRowset, respectively.
             */
            if (is_null($options['mode'])) {
                // belongs to
                try {
                    $model->getReference($modelName, $options['rule']);
                    $relatedRowset = $row->findParentRow(
                        $otherModel, $options['rule'], $conditions
                    );
                } catch(Exception $e) {
                    if (!Garp_Content_Relation_Manager::isInvalidReferenceException($e)) {
                        throw $e;
                    }
                    try {
                        // one to many - one to one
                        // The following line triggers an exception if no reference is available
                        $otherModel->getReference(get_class($model), $options['rule']);
                        $relatedRowset = $row->findDependentRowset(
                            $otherModel,
                            $options['rule'],
                            $conditions
                        );
                    } catch (Exception $e) {
                        if (!Garp_Content_Relation_Manager::isInvalidReferenceException($e)) {
                            throw $e;
                        }
                        $bindingModel = $model->getBindingModel($modelName);
                        $relatedRowset = $row->findManyToManyRowset(
                            $otherModel,
                            $bindingModel,
                            $options['rule2'],
                            $options['rule'],
                            $conditions
                        );
                    }
                }
            } else {
                switch ($options['mode']) {
                case 'parent':
                    $relatedRowset = $row->findParentRow(
                        $otherModel,
                        $options['rule'],
                        $conditions
                    );
                    break;
                case 'dependent':
                    $relatedRowset = $row->findDependentRowset(
                        $otherModel,
                        $options['rule'],
                        $conditions
                    );
                    break;
                default:
                    throw new Garp_Model_Exception(
                        'Invalid value for "mode" given. Must be either "parent" or ' .
                            '"dependent", but "' . $options['mode'] . '" was given.'
                    );
                    break;
                }
            }
        }
        // Reset the cacheQueries value. It's a static property,
        // so leaving it FALSE will affect all future fetch() calls to this
        // model. Not good.
        $otherModel->setCacheQueries($originalCacheQueriesFlag);
        return $relatedRowset;
    }
1105512
function executeRelatedResultWithQuery (result, relatedName) {
    // get custom args for related model query
    var relatedArgs = this.args.with[relatedName]
    // get relation
    var relation = this.model.relation(relatedName)
    // build query args
    var queryArgs = {
        all: true,
        allow: this.args.allow,
        session: this.session,
        where: { relation: { name: this.model.name } },
    }
    // relation is via a linking table
    if (relation.via) {
        queryArgs.where.relation[relation.viaModelIdColumn] = result[relation.modelIdColumn] || result[model.defaultColumns[relation.modelIdColumn]]
    }
    // relation is direct
    else {
        queryArgs.where.relation[relation.relationIdColumn] = result[relation.modelIdColumn] || result[model.defaultColumns[relation.modelIdColumn]]
    }
    // merge args
    if (typeof relatedArgs === 'object') {
        _.merge(queryArgs, relatedArgs)
    }
    // do query on related model
    return relation.model.query(queryArgs)
    // merge related records once loaded
    .then(results => {
        result._related[relatedName] = results
    })
}
358349
public function setResult(&$result) {
        if (!is_null($result) &&
            !$result instanceof Garp_Db_Table_Row &&
            !$result instanceof Garp_Db_Table_Rowset) {
                throw new InvalidArgumentException(__METHOD__.' expects parameter 1 to be a'.
                    'Garp_Db_Table_Row or Garp_Db_Table_Rowset. '.gettype($result).' given.');
        }
        $this->_result = $result;
    }
978764
protected function queryEagerLoadedRelationships(array $results, array $eagerLoads) : array
    {
        $this->eagerLoads = $this->parseRelations($eagerLoads);

        return $this->eagerLoadRelations($results);
    }
981004
protected function loadRelationOrder(Builder $query, $direction)
    {
        /** @var Relation $relationClass */
        $relations = collect(explode('.', $this->name));
        $loop = 0;
        if ($relations->count() >= 2) {
            $query->select($query->getModel()->getTable().'.*');

            do {
                $model = ! $loop++ ? $query->getModel() : $relationClass->getModel();
                $relation = $relations->shift();

                if (method_exists($model, $relation)) {
                    $relationClass = $model->{$relation}();
                    $relationModel = $relationClass->getRelated();

                    $loadRelationMethod = implode('', ['load', class_basename(get_class($relationClass))]);
                    call_user_func([$this, $loadRelationMethod],
                        $relations, $relationClass, $relationModel, $model, $query, $direction);
                } else {
                    break;
                }
            } while (true);

            if ($this->sortedColumnAlias) {
                $query->orderBy(DB::raw($this->sortedColumnAlias), $direction);
            }
        }
    }
937032
public function eagerlyResult(&$result, $relation, $subRelation, $closure)
    {
        $morphKey  = $this->morphKey;
        $morphType = $this->morphType;
        // 多态类型映射
        $model = $this->parseModel($result->{$this->morphType});

        $this->eagerlyMorphToOne($model, $relation, $result, $subRelation);
    }
187590
protected function cacheSingleRelationResult(string $parentKey, string $relation, $result, Relationship $relationship)
    {
        $this->cache[$parentKey][$relation] = $this->getCachedRelationship($relation, $result, $relationship);
    }
1272224
protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure)
    {
        $localKey   = $this->localKey;
        $foreignKey = $this->foreignKey;

        $range = [];
        foreach ($resultSet as $result) {
            // 获取关联外键列表
            if (isset($result->$localKey)) {
                $range[] = $result->$localKey;
            }
        }

        if (!empty($range)) {
            $this->query->removeWhereField($foreignKey);

            $data = $this->eagerlyWhere([
                [$foreignKey, 'in', $range],
            ], $foreignKey, $relation, $subRelation, $closure);

            // 关联属性名
            $attr = Db::parseName($relation);

            // 关联数据封装
            foreach ($resultSet as $result) {
                // 关联模型
                if (!isset($data[$result->$localKey])) {
                    $relationModel = null;
                } else {
                    $relationModel = $data[$result->$localKey];
                    $relationModel->setParent(clone $result);
                    $relationModel->isUpdate(true);
                }

                if (!empty($this->bindAttr)) {
                    // 绑定关联属性
                    $this->bindAttr($relationModel, $result, $this->bindAttr);
                } else {
                    // 设置关联属性
                    $result->setRelation($attr, $relationModel);
                }
            }
        }
    }
1057476
public function findRelation($entity, $relationName, $orderColumn = null, $orderType = null) {
        $this->load();
        $mapper = MapperBuilder::buildFromClassName($this->mapping, get_class($entity));
        $relation = $mapper->getRelation($relationName);
        $relationMapper = MapperBuilder::buildFromName($this->mapping, $relation->getEntity());

        $selectQuery = "SELECT tmain.* FROM `" . $relationMapper->getTable() . "` tmain ";
        $joinQuery = QueryBuilder::buildJoinRelationQuery($relation, "tmain", "j1");
        $whereQuery = "WHERE j1." . $relation->getLocalColumn() . " = '" . $entity->getId() . "'";

        $orderQuery = "";
        if (!is_null($orderColumn)) {
            $orderQuery .= "ORDER BY $orderColumn ";
            if (!is_null($orderType)) {
                $orderQuery .= "$orderType";
            } else {
                $orderQuery .= "ASC";
            }
        }

        $query = $selectQuery . $joinQuery . $whereQuery . $orderQuery;
        $queryResult = $this->getDataSource()->query($query);
        if ($queryResult) {
            $collection = new Collection($relationMapper->getClass(), $queryResult, $relationMapper);
        } else {
            $collection = new Collection($relationMapper->getClass(), array(), $relationMapper);
        }

        return $collection;
    }
1338542
protected static function _initRelationship($relationship, $options)
    {
        $classShortName = basename(str_replace('\\', '/', static::$rootClass));

        // apply defaults
        if (empty($options['type'])) {
            $options['type'] = 'one-one';
        }
        
        if ($options['type'] == 'one-one') {
            if (empty($options['local'])) {
                $options['local'] = $relationship . 'ID';
            }
                
            if (empty($options['foreign'])) {
                $options['foreign'] = 'ID';
            }
        } elseif ($options['type'] == 'one-many') {
            if (empty($options['local'])) {
                $options['local'] = 'ID';
            }
                    
            if (empty($options['foreign'])) {
                $options['foreign'] =  $classShortName. 'ID';
            }
                
            if (!isset($options['indexField'])) {
                $options['indexField'] = false;
            }
                
            if (!isset($options['conditions'])) {
                $options['conditions'] = [];
            } elseif (is_string($options['conditions'])) {
                $options['conditions'] = [$options['conditions']];
            }
                
            if (!isset($options['order'])) {
                $options['order'] = false;
            }
        } elseif ($options['type'] == 'context-children') {
            if (empty($options['local'])) {
                $options['local'] = 'ID';
            }
                    
            if (empty($options['contextClass'])) {
                $options['contextClass'] = get_called_class();
            }
                
            if (!isset($options['indexField'])) {
                $options['indexField'] = false;
            }
                
            if (!isset($options['conditions'])) {
                $options['conditions'] = [];
            }
                
            if (!isset($options['order'])) {
                $options['order'] = false;
            }
        } elseif ($options['type'] == 'context-parent') {
            if (empty($options['local'])) {
                $options['local'] = 'ContextID';
            }
                    
            if (empty($options['foreign'])) {
                $options['foreign'] = 'ID';
            }

            if (empty($options['classField'])) {
                $options['classField'] = 'ContextClass';
            }

            if (empty($options['allowedClasses'])) {
                $options['allowedClasses'] = static::$contextClasses;
            }
        } elseif ($options['type'] == 'many-many') {
            if (empty($options['class'])) {
                throw new Exception('Relationship type many-many option requires a class setting.');
            }
        
            if (empty($options['linkClass'])) {
                throw new Exception('Relationship type many-many option requires a linkClass setting.');
            }
                
            if (empty($options['linkLocal'])) {
                $options['linkLocal'] = $classShortName . 'ID';
            }
        
            if (empty($options['linkForeign'])) {
                $foreignShortname = basename(str_replace('\\', '/', $options['class']::$rootClass));
                $options['linkForeign'] =  $foreignShortname . 'ID';
            }
        
            if (empty($options['local'])) {
                $options['local'] = 'ID';
            }

            if (empty($options['foreign'])) {
                $options['foreign'] = 'ID';
            }

            if (!isset($options['indexField'])) {
                $options['indexField'] = false;
            }
                
            if (!isset($options['conditions'])) {
                $options['conditions'] = [];
            }
                
            if (!isset($options['order'])) {
                $options['order'] = false;
            }
        }
           
        if (static::isVersioned() && $options['type'] == 'history') {
            if (empty($options['class'])) {
                $options['class'] = get_called_class();
            }
        }
                
        return $options;
    }
892650
public function matchRelation($model, $results) {
        $data = [];
        foreach ($results as $item) {
            if (!$this->isMatchRelation($model, $item)) {
                continue;
            }
            if ($this->type == self::TYPE_ONE) {
                return $item;
            }
            $data[] = $item;
        }
        return $data;
    }
929083
private function joinWithRelations($model, $with, $joinType)
    {
        $relations = [];

        foreach ($with as $name => $callback) {
            if (is_int($name)) {
                $name = $callback;
                $callback = null;
            }

            $primaryModel = $model;
            $parent = $this;
            $prefix = '';
            while (($pos = strpos($name, '.')) !== false) {
                $childName = substr($name, $pos + 1);
                $name = substr($name, 0, $pos);
                $fullName = $prefix === '' ? $name : "$prefix.$name";
                if (!isset($relations[$fullName])) {
                    $relations[$fullName] = $relation = $primaryModel->getRelation($name);
                    $this->joinWithRelation($parent, $relation, $this->getJoinType($joinType, $fullName));
                } else {
                    $relation = $relations[$fullName];
                }
                $primaryModel = new $relation->modelClass;
                $parent = $relation;
                $prefix = $fullName;
                $name = $childName;
            }

            $fullName = $prefix === '' ? $name : "$prefix.$name";
            if (!isset($relations[$fullName])) {
                $relations[$fullName] = $relation = $primaryModel->getRelation($name);
                if ($callback !== null) {
                    call_user_func($callback, $relation);
                }
                if (!empty($relation->joinWith)) {
                    $relation->buildJoinWith();
                }
                $this->joinWithRelation($parent, $relation, $this->getJoinType($joinType, $fullName));
            }
        }
    }
287598
public function eagerlyResult(&$result, $relation, $subRelation, $closure)
    {
        $pk = $result->getPk();

        if (isset($result->$pk)) {
            $key   = $result->$pk;
            $where = [
                [$this->morphKey, '=', $key],
                [$this->morphType, '=', $this->type],
            ];
            $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure);

            if (!isset($data[$key])) {
                $data[$key] = [];
            }

            foreach ($data[$key] as &$relationModel) {
                $relationModel->setParent(clone $result);
                $relationModel->isUpdate(true);
            }

            $result->setRelation(Db::parseName($relation), $this->resultSetBuild($data[$key]));
        }
    }
1235000
public function eagerlyResult(&$result, $relation, $subRelation, $closure, $join = false)
    {
        if (0 == $this->eagerlyType || $join) {
            // 模型JOIN关联组装
            $this->match($this->model, $relation, $result);
        } else {
            // IN查询
            $this->eagerlyOne($result, $relation, $subRelation, $closure);
        }
    }