一对多关联到Mongo上

@param string $linkMongoName
@param mixed $relation
@param array $fields
@param mixed $where
@param mixed $sort
@return Row
517798
public function joinMongo($linkMongoName, $relation, array $fields = [], $where = [], $sort = []): Row
    {
        if (empty($fields)) {
            return $this;
        }

        // 生成关联表模型对象
        $mongo = mongo($linkMongoName);

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

        // 如果本表关联字段不存在
        if (!array_key_exists($field, $this->data)) {
            trigger_error('关联Mongo时,主表字段不存在:' . $field, E_USER_ERROR);
        }

        // 生成本表关联字段的所有值
        $key = $this->data[$field];

        // 生成关联表查询条件
        $where[] = [
            $linkField => $key
        ];

        // 关联表查询结果
        $fields[$linkField] = true;
        if (!array_key_exists('_id', $fields)) {
            $fields['_id'] = false;
        }

        // 生成查询游标
        try {
            $cursor = $mongo->getCollection()->find($where, $fields)->sort($sort);
        } catch (\MongoCursorException $e) {
            trigger_error('Mongo查询失败:' . $linkMongoName, E_USER_ERROR);
            return $this;
        }

        // 生成以关联表关联字段值为键的数组
        $linkResult = [];
        try {
            while ($cursor->hasNext()) {
                $row = $cursor->getNext();
                $linkResult[] = $row;
            }
        } catch (\MongoConnectionException $e) {
            trigger_error('Mongo服务器连接失败:' . $linkMongoName, E_USER_ERROR);
        } catch (\MongoCursorTimeoutException $e) {
            trigger_error('Mongo游标读取超时:' . $linkMongoName, E_USER_ERROR);
        }

        $this->data[$linkMongoName] = $linkResult;

        return $this;
    }
1254076
public function whereHas($relation, array $where = [], array $whereHas = [], array $with = [])
    {
        $query = $this->make($with);

        $query = $this->applyWhere($query, $where);

        $query = $query->whereHas($relation, function ($q) use ($whereHas) {
            $this->applyWhere($q, $whereHas);
        });

        return $query->get();
    }
761040
public static function where($column, $relation, $value)
    {
        $self = self::instance();
        $key = $self->_keyName;
        $data = \Query::from($self->_table)->select($key)->where($column, $relation, $value)->get();
        $rows = [];

        if (!is_null($data)) {
            foreach ($data as $item) {
                $rows[] = self::instance($item->$key);
            }
        }

        return $rows;
    }
1277811
public function createMangoIndex($fields, $ddoc = null, $name = null)
  {
      $documentPath = '/'.$this->databaseName.'/_index';

      $params = ['index'=>['fields'=>$fields]];

      if ($ddoc) {
          $params['ddoc'] = $ddoc;
      }
      if ($name) {
          $params['name'] = $name;
      }

      return $this->httpClient->request('POST', $documentPath, json_encode($params));
  }
83486
public function relationCount(&$result, $relations, $aggregate = 'sum', $field = '*')
    {
        foreach ($relations as $key => $relation) {
            $closure = null;

            if ($relation instanceof \Closure) {
                $closure  = $relation;
                $relation = $key;
            } elseif (is_string($key)) {
                $name     = $relation;
                $relation = $key;
            }

            $relation = Db::parseName($relation, 1, false);
            $count    = $this->$relation()->relationCount($result, $closure, $aggregate, $field);

            if (!isset($name)) {
                $name = Db::parseName($relation) . '_' . $aggregate;
            }

            $result->setAttr($name, $count);
        }
    }
329841
public function all($fields = [], $sorted = true, $sortBy = 'cn', $sortByDirection = 'asc')
    {
        $personCategory = $this->adldap->getPersonFilter('category');
        $person = $this->adldap->getPersonFilter('person');

        $search = $this->adldap->search()
            ->select($fields)
            ->where($personCategory, '=', $person)
            ->where('samaccounttype', '=', Adldap::ADLDAP_NORMAL_ACCOUNT);

        if ($sorted) {
            $search->sortBy($sortBy, $sortByDirection);
        }

        return $search->get();
    }
1200123
public function gridFieldForElementRelation(
        FieldList $fields,
        $relationName,
        $relation
    ) {
        // sort relations
        asort($relation);

        $config = GridFieldConfig_RelationEditor::create()
            ->removeComponentsByType(GridFieldDeleteAction::class)
            ->removeComponentsByType(GridFieldAddExistingAutocompleter::class)
            ->addComponent(new GridFieldOrderableRows('Sort'))
            ->addComponent(new GridFieldDeleteAction())
        ;

        // attach default elements action
        if (
            $this->owner->canEdit()
            && !$this->defaultsCreated()
            && $this->owner->getDefaultElements()
            && isset($this->owner->getDefaultElements()[$relationName])
        ) {
            $config->addComponent(new GridFieldDefaultElementsButton());
        }

        if (count($relation) > 1)
        {
            $config
                ->removeComponentsByType(GridFieldAddNewButton::class)
                ->addComponent($multiClass = new GridFieldAddNewMultiClass())
            ;

            $multiClass->setClasses(ElementsExtension::map_classnames($relation));
        }

        $config
            ->getComponentByType(GridFieldPaginator::class)
            ->setItemsPerPage(150)
        ;

        $columns = [
            'CMSTypeInfo' => _t(__CLASS__ . '.CMSTypeInfo', 'Type'),
            'CMSSummary' => _t(__CLASS__ . '.CMSSummary', 'Summary'),
        ];

        if ($this->owner->hasExtension(ElementBase::FLUENT_CLASS))
        {
            $columns['Languages'] = _t(__CLASS__ . '.Languages', 'Lang');
        }

        if (count($relation) == 1
            && $summaryFields = singleton($relation[0])->summaryFields()
        ) {
            $columns = array_merge($columns, $summaryFields);
        }

        $config
            ->getComponentByType(GridFieldDataColumns::class)
            ->setDisplayFields($columns)
        ;

        $tabName = "Root.{$relationName}";

        // if only one relation is set, add gridfield to main tab
        if (count($this->elementRelations) == 1) $tabName = "Root.Main";

        $label = _t("Element_Relations.{$relationName}", $relationName);

        $config
            ->getComponentByType(GridFieldDetailForm::class)
            ->setItemRequestClass(VersionedElement_ItemRequest::class)
        ;

        $fields->addFieldToTab(
            $tabName,
            $gridField = GridField::create(
                $relationName,
                $label,
                $this->owner->ElementsByRelation($relationName),
                $config
            )
        );

        $gridField->addExtraClass('elements-gridfield');


        if (count($relation) == 1) $gridField->setModelClass($relation[0]);

        if (count($this->elementRelations) > 1)
        {
            $fields
                ->findOrMakeTab($tabName)
                ->setTitle($label)
            ;
        }
        return $this->owner;
    }
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);
                }
            }
        }
    }
584409
public function match(array $results, $relation)
    {
        $foreign = $this->foreignKey;

        $other = $this->otherKey;

        // Execute the relationship and get related entities as an EntityCollection
        $entities = $this->getEager();

        // First we will get to build a dictionary of the child models by their primary
        // key of the relationship, then we can easily match the children back onto
        // the parents using that dictionary and the primary key of the children.
        $dictionary = [];

        // TODO ; see if otherKey is the primary key of the related entity, we can
        // simply use the EntityCollection key to match entities to results, which
        // will be much more efficient, and use this method as a fallback if the
        // otherKey is not the same as the primary Key.
        foreach ($entities as $entity) {
            $entity = $this->factory->make($entity);
            $dictionary[$entity->getEntityAttribute($other)] = $entity->getObject();
        }

        // Once we have the dictionary constructed, we can loop through all the parents
        // and match back onto their children using these keys of the dictionary and
        // the primary key of the children to map them onto the correct instances.
        return array_map(function ($result) use ($dictionary, $foreign, $relation) {
            if (array_key_exists($foreign, $result) && isset($dictionary[$result[$foreign]])) {
                $result[$relation] = $dictionary[$result[$foreign]];
            } else {
                $result[$relation] = null;
            }

            return $result;
        }, $results);
    }
267222
protected function getLinkParams($linkName = null, $entityName = null, $isOrmEntityDefs = false, $returns = null)
    {
        if (!isset($linkName)) {
            $linkName = $this->getLinkName();
        }
        if (!isset($entityName)) {
            $entityName = $this->getEntityName();
        }

        $entityDefs = $this->getDefs($isOrmEntityDefs);
        $relationKeyName = $isOrmEntityDefs ? 'relations' : 'links';

        if (isset($entityDefs[$entityName]) && isset($entityDefs[$entityName][$relationKeyName][$linkName])) {
            return $entityDefs[$entityName][$relationKeyName][$linkName];
        }

        return $returns;
    }
51820
protected function sortOrderQuery(
        array $sortOrderCondition = [],
        string $sortOrderAttribute = 'sortOrder'
    ): ActiveQuery {
        return static::find()
            ->andWhere($sortOrderCondition)
            ->orderBy([
                $sortOrderAttribute => SORT_ASC,
                'dateUpdated' => SORT_DESC
            ]);
    }
612441
protected function getManyToManyRelationSelectSQL(RelationDescription $relationDescription,
          array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null,
          PagingInfo $pagingInfo=null) {
    $thisRelationDesc = $relationDescription->getThisEndRelation();
    $otherRelationDesc = $relationDescription->getOtherEndRelation();
    $nmMapper = self::getMapper($thisRelationDesc->getOtherType());
    $otherFkAttr = $nmMapper->getAttribute($otherRelationDesc->getFkName());
    $nmTableName = $nmMapper->getRealTableName();

    // id parameters
    $parameters = [];
    $idPlaceholder = ':'.$nmTableName.'_'.$otherFkAttr->getName();
    for ($i=0, $count=sizeof($otherObjectProxies); $i<$count; $i++) {
      $dbid = $otherObjectProxies[$i]->getValue($otherRelationDesc->getIdName());
      if ($dbid === null) {
        $dbid = SQLConst::NULL();
      }
      $parameters[$idPlaceholder.$i] = $dbid;
    }

    // statement
    $queryId = $this->getCacheKey($otherRole.sizeof($otherObjectProxies), null, $criteria, $orderby, $pagingInfo);
    $selectStmt = SelectStatement::get($this, $queryId);
    if (!$selectStmt->isCached()) {
      // initialize the statement

      $thisFkAttr = $nmMapper->getAttribute($thisRelationDesc->getFkName());
      $thisIdAttr = $this->getAttribute($thisRelationDesc->getIdName());

      $tableName = $this->getRealTableName();
      $selectStmt->from($tableName, '');
      $this->addColumns($selectStmt, $tableName);
      $joinCond = $nmTableName.'.'.$thisFkAttr->getColumn().'='.$tableName.'.'.$thisIdAttr->getColumn();
      $joinColumns = [];
      $selectStmt->where($this->quoteIdentifier($nmTableName).'.'.
          $this->quoteIdentifier($otherFkAttr->getColumn()).' IN('.join(',', array_keys($parameters)).')');
      // order (in this case we use the order of the many to many objects)
      $nmSortDefs = $nmMapper->getDefaultOrder($otherRole);
      $hasNmOrder = sizeof($nmSortDefs) > 0;
      $orderType = $hasNmOrder ? $nmMapper->getType() : $this->getType();
      $orderTable = $hasNmOrder ? $nmTableName : $tableName;
      $defaultOrderDef = $hasNmOrder ? $nmSortDefs : $this->getDefaultOrder($otherRole);
      $this->addOrderBy($selectStmt, $orderby, $orderType, $orderTable, $defaultOrderDef);
      foreach($nmSortDefs as $nmSortDef) {
        // add the sort attribute from the many to many object
        $nmSortAttributeDesc = $nmMapper->getAttribute($nmSortDef['sortFieldName']);
        $joinColumns[$nmSortAttributeDesc->getName()] = $nmSortAttributeDesc->getColumn();
      }
      // add proxy id
      $joinColumns[self::INTERNAL_VALUE_PREFIX.'id'] = $otherFkAttr->getColumn();
      $selectStmt->join($nmTableName, $joinCond, $joinColumns);
      // additional conditions
      $parameters = array_merge($parameters, $this->addCriteria($selectStmt, $criteria, $nmTableName));
      // limit
      if ($pagingInfo != null) {
        $selectStmt->limit($pagingInfo->getPageSize());
      }
    }
    else {
      // on used statements only set parameters
      $parameters = array_merge($parameters, $this->getParameters($criteria, $nmTableName));
    }

    // set parameters
    $selectStmt->setParameters($parameters);

    // always update offset, since it's most likely not contained in the cache id
    if ($pagingInfo != null) {
      $selectStmt->offset($pagingInfo->getOffset());
    }
    return [$selectStmt, $otherRelationDesc->getIdName(), self::INTERNAL_VALUE_PREFIX.'id'];
  }
51344
static public function listSort($list, $field, $sort = 'asc')
    {
        if (is_array($list)) {
            $refer = array();
            $resultSet = array();
            foreach ($list as $i => $data)
                $refer[$i] = &$data[$field];
            switch ($sort) {
                case 'asc':
                    asort($refer);
                    break;
                case 'desc':
                    arsort($refer);
                    break;
            }
            foreach ($refer as $key => $val)
                $resultSet[] = &$list[$key];
            return $resultSet;
        }
        return false;
    }
911460
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure)
    {
        $morphKey  = $this->morphKey;
        $morphType = $this->morphType;
        $range     = [];

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

        if (!empty($range)) {
            // 关联属性名
            $attr = Db::parseName($relation);

            foreach ($range as $key => $val) {
                // 多态类型映射
                $model = $this->parseModel($key);
                $obj   = new $model;
                $pk    = $obj->getPk();
                $list  = $obj->all($val, $subRelation);
                $data  = [];

                foreach ($list as $k => $vo) {
                    $data[$vo->$pk] = $vo;
                }

                foreach ($resultSet as $result) {
                    if ($key == $result->$morphType) {
                        // 关联模型
                        if (!isset($data[$result->$morphKey])) {
                            $relationModel = null;
                        } else {
                            $relationModel = $data[$result->$morphKey];
                            $relationModel->setParent(clone $result);
                            $relationModel->isUpdate(true);
                        }

                        $result->setRelation($attr, $relationModel);
                    }
                }
            }
        }
    }
599968
public function mapMongo(string $linkMongoName, $relation, array $fields = []): Result
    {
        if (empty($fields)) {
            return $this;
        }

        // 生成关联表模型对象
        $mongo = mongo($linkMongoName);

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

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

        // 生成关联表查询条件
        $where = [
            $linkField => [
                '$in' => $keys
            ]
        ];

        // 关联表查询结果
        $fields[$linkField] = true;
        if (!array_key_exists('_id', $fields)) {
            $fields['_id'] = false;
        }

        // 生成查询游标
        $cursor = $mongo->getCollection()->find($where, $fields);

        // 生成以关联表关联字段值为键的数组
        $linkResult = [];
        try {
            while ($cursor->hasNext()) {
                $row = $cursor->getNext();
                $linkResult[$row[$linkField]] = $row;
            }
        }catch (\MongoConnectionException $e){
            trigger_error('Mongo服务器连接失败:'.$linkMongoName,E_USER_ERROR);
        }catch (\MongoCursorTimeoutException $e){
            trigger_error('Mongo游标读取超时:'.$linkMongoName,E_USER_ERROR);
        }

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

            foreach ($fields as $k => $v) {
                // 如果本表已经有此字段,则不扩展
                if ($v and !array_key_exists($k, $row)) {
                    // 扩展值为空,因为没有对应
                    $row[$k] = null;
                }
            }

            $this->data[$key] = $row;
        }

        $this->row2Obj();
        return $this;
    }
601826
public function row($fields = null, $where = null, $orderBy = null)
    {
        // 查询数据
        $row = parent::row($fields, $where, $orderBy);

        // 如果没有查到
        if (!$row) {
            $row = [];
        }
        // 构造 为行对象
        return new Row($this->tableName, $row);
    }
1158935
public function join($linkTableName, $relation, $fields = '*', $where = [], $orderBy = ''): Result
    {
        // 生成关联表模型对象
        $table = $this->makeTable($linkTableName);

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

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

        // 生成关联表查询条件
        // $where [] = $table->markField ( $linkField ) . ' in ("' . join ( '","', $keys ) . '") ';
        $where[$linkField . ' in'] = $keys;

        // 关联表查询结果
        $linkData = $table->select($this->append($fields, $linkField), $where, $orderBy)
            ->all();

        // 生成以关联表关联字段值为键的数组
        $linkResult = [];
        foreach ($linkData as $row) {
            $key = $row[$linkField];
            if (!array_key_exists($key, $linkResult)) {
                $linkResult[$key] = [];
            }
            $linkResult[$key][] = $row;
        }

        // 转换每个值对应的结果为结果集对象
        foreach ($linkResult as $k => $v) {
            $linkResult[$k] = new Result($linkTableName, $v);
        }

        // 为本表每行数据补充字段
        foreach ($this->data as $key => $row) {

            // 如果没有对应的关联,则不扩展
            if (array_key_exists($row[$field], $linkResult)) {
                // 关联的记录
                $linkObj = $linkResult[$row[$field]];

                // 如果本表已经有此字段,则不扩展
                if (!array_key_exists($table->name(), $row)) {
                    $row[$table->alias()] = $linkObj;
                }

                $this->data[$key] = $row;
            }
        }

        $this->row2Obj();
        return $this;
    }