多态MorphTo 关联模型预查询
@access public
@param  object $model       关联模型对象
@param  string $relation    关联名
@param  Model  $result
@param  string $subRelation 子关联
@return void
519739
protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '')
    {
        // 预载入关联查询 支持嵌套预载入
        $pk   = $this->parent->{$this->morphKey};
        $data = (new $model)->with($subRelation)->find($pk);

        if ($data) {
            $data->setParent(clone $result);
            $data->isUpdate(true);
        }

        $result->setRelation(Db::parseName($relation), $data ?: null);
    }
1252456
protected function isEmbeddable($model, $relation)
    {
        if (array_key_exists($model, $this->embeddedRecords)) {
            return is_array($this->embeddedRecords[$model]) && in_array($relation, $this->embeddedRecords[$model]);
        }

        return false;
    }
917183
protected function eagerlyOne(&$result, $relation, $subRelation, $closure)
    {
        $localKey   = $this->localKey;
        $foreignKey = $this->foreignKey;
        $data       = $this->eagerlyWhere([
            [$foreignKey, '=', $result->$localKey],
        ], $foreignKey, $relation, $subRelation, $closure);

        // 关联模型
        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(Db::parseName($relation), $relationModel);
        }
    }
560661
protected function addPolymorphicRelation(string $relation)
    {
        if (!in_array($relation, $this->polymorphicRelations)) {
            $this->polymorphicRelations[] = $relation;
        }
    }
515147
public function setRelation($relation, $value)
    {
        $this->checkInitRelations();
        $this->relations[$relation] = $value;

        return $this;
    }
94324
public function belongsTo($related, $foreignKey = null, $otherKey = null, $relation = null)
    {
        // If no relation name was given, we will use this debug backtrace to extract
        // the calling method's name and use that as the relationship name as most
        // of the time this will be what we desire to use for the relationships.
        if (is_null($relation)) {
            list($current, $caller) = debug_backtrace(false, 2);

            $relation = $caller['function'];
        }

        $specialType = $this->getCmsSpecialRelationType($relation);

        // If no foreign key was supplied, we can use a backtrace to guess the proper
        // foreign key name by using the name of the relationship function, which
        // when combined with an "_id" should conventionally match the columns.
        if (is_null($foreignKey)) {
            if ($specialType === self::RELATION_TYPE_CATEGORY) {
                $foreignKey = config('pxlcms.relations.categories.keys.category');
            } else {
                $foreignKey = Str::snake($relation);
            }
        }

        // If the relation name is the same (casing aside) as the foreign key attribute,
        // we must make sure that the foreign key property is at least a key in the
        // attributes array. If it is not, there will be recursive lookup problems
        // where the attribute is never resolved and 'property does not exist' exceptions occur.
        if (    snake_case($relation) == $foreignKey
            &&  ! array_key_exists($foreignKey, $this->attributes)
        ) {
            $this->attributes[ $foreignKey ] = null;
        }

        /** @var Builder $belongsTo */
        $belongsTo = parent::belongsTo($related, $foreignKey, $otherKey, $relation);

        // category relation must be filtered by module id
        if ($specialType === self::RELATION_TYPE_CATEGORY) {
            $belongsTo->where(
                config('pxlcms.relations.categories.keys.module'),
                $this->getModuleNumber()
            );
        }

        return $belongsTo;
    }
261827
public function getRelation($subRelation = '', $closure = null)
    {
        $localKey = $this->localKey;

        if ($closure) {
            $closure($this->query);
        }

        // 判断关联类型执行查询
        $relationModel = $this->query
            ->removeWhereField($this->foreignKey)
            ->where($this->foreignKey, $this->parent->$localKey)
            ->relation($subRelation)
            ->find();

        if ($relationModel) {
            $relationModel->setParent(clone $this->parent);
        }

        return $relationModel;
    }
345372
public function add(Relations\Relation $relation)
    {
        if (isset($this->relations[$relation->getName()])) {
            throw new \Exception(
                'Relation '.$relation->getName().' is duplicated in model '.$relation->getParentModel()->getName()
            );
        }

        $this->relations[$relation->getName()] = $relation;
    }
858682
public function duplicate($model, $relation = null) {
		$clone = $this->cloneModel($model);
		$this->duplicateAttachments($clone);
		$this->saveClone($clone, $relation, $model);
		$this->cloneRelations($model, $clone);
		return $clone;
	}
124705
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure)
    {
        $morphType = $this->morphType;
        $morphKey  = $this->morphKey;
        $type      = $this->type;
        $range     = [];

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

        if (!empty($range)) {
            $data = $this->eagerlyMorphToOne([
                [$morphKey, 'in', $range],
                [$morphType, '=', $type],
            ], $relation, $subRelation, $closure);

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

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

                $result->setRelation($attr, $relationModel);
            }
        }
    }
929226
protected function getJoinClause(Model $parent, Relation $relation, $table, $type)
    {
        list($fk, $pk) = $this->getJoinKeys($relation);

        $join = (new Join($this->query, $type, $table))->on($fk, '=', $pk);

        if (in_array(SoftDeletes::class, class_uses_recursive($relation->getRelated()))) {
            $join->whereNull($relation->getRelated()->getQualifiedDeletedAtColumn());
        }

        if ($relation instanceof MorphOneOrMany) {
            $join->where($relation->getQualifiedMorphType(), '=', $parent->getMorphClass());
        } elseif ($relation instanceof MorphToMany || $relation instanceof MorphByMany) {
            $join->where($relation->getMorphType(), '=', $parent->getMorphClass());
        }

        return $join;
    }
206931
protected function relation_changed(Model $obj, $relation)
	{
		// Check that the relation exists
		if ($obj->relations($relation) === false)
		{
			throw new \InvalidArgumentException('Unknown relation '.$relation);
		}

		// If the relation is not loaded then ignore it.
		if ( ! $obj->is_fetched($relation))
		{
			return false;
		}

		$relation_object = $obj->relations($relation);

		// Check if whe have a singular relation
		if ($relation_object->is_singular())
		{
			// If so check that one model
			return $obj->{$relation}->is_changed();
		}

		// Else we have an array of related objects so start checking them all
		foreach ($obj->{$relation} as $related_model)
		{
			if ($related_model->is_changed())
			{
				return true;
			}
		}

		return false;
	}
711064
public function match(array $models, Collection $results, $relation)
    {
        // This relationship deals with a One-To-One morph type so we'll just extract
        // the first model out of the results and return it.
        $matched = parent::match($models, $results, $relation);

        return array_map(function($match) use ($relation)
        {
            if (isset($match[$relation]) && isset($match[$relation][0]))
            {
                $match->setRelation($relation, $match[$relation][0]);
            }

            return $match;

        }, $matched);
    }
744701
protected function getNestedRelation(Model $model, $relationArray)
    {
        $relation = null;
        foreach ($relationArray as $relation) {
            /** @var Relation $relation */
            $relation = $model->newQuery()->getRelation($relation);
            if ($relation) {
                $model = $relation->getRelated();
            }
        }

        return $relation;
    }
14667
private function manyToMany(&$structure, $property, $relation)
    {
        $from = $structure->name;
        $to = $relation->targetEntity;

        $reflection = null;
        try {
            $reflection = new \ReflectionClass($to);
        }catch(\Exception $e) { // @codeCoverageIgnore
            // Ignore, we will throw error on the next if. // @codeCoverageIgnore
        }
        if ($reflection === null || ! $reflection->isSubclassOf(Entity::class)) {
            throw new RelationException("The target entity of your relation on the entity '".$from."' and property '".$property->getName()."' has an unknown target Entity!"); // @codeCoverageIgnore
        }

        /** @var JoinTable $join */
        $join = $this->getJoin($property, JoinTable::class);
        $join->sourceEntityName = $from;
        $join->targetEntityName = $to;

        $relation->join = $join;

        // Register the join table
        EntityManager::getInstance()->registerJoinTable($join);

        // Add declaration to the structure
        $structure->relationProperties[] = $property->getName();
        $structure->foreignColumnNames[] = $join->column;
        $structure->relations[$property->getName()] = $relation;
    }
1177051
protected function joinEagerLoadedColumn($relation, $relationColumn)
    {
        $table     = '';
        $deletedAt = false;
        $lastQuery = $this->query;
        foreach (explode('.', $relation) as $eachRelation) {
            $model = $lastQuery->getRelation($eachRelation);
            switch (true) {
                case $model instanceof BelongsToMany:
                    $pivot   = $model->getTable();
                    $pivotPK = $model->getExistenceCompareKey();
                    $pivotFK = $model->getQualifiedParentKeyName();
                    $this->performJoin($pivot, $pivotPK, $pivotFK);

                    $related = $model->getRelated();
                    $table   = $related->getTable();
                    $tablePK = $related->getForeignKey();
                    $foreign = $pivot . '.' . $tablePK;
                    $other   = $related->getQualifiedKeyName();

                    $lastQuery->addSelect($table . '.' . $relationColumn);
                    $this->performJoin($table, $foreign, $other);

                    break;

                case $model instanceof HasOneOrMany:
                    $table     = $model->getRelated()->getTable();
                    $foreign   = $model->getQualifiedForeignKeyName();
                    $other     = $model->getQualifiedParentKeyName();
                    $deletedAt = $this->checkSoftDeletesOnModel($model->getRelated());
                    break;

                case $model instanceof BelongsTo:
                    $table     = $model->getRelated()->getTable();
                    $foreign   = $model->getQualifiedForeignKeyName();
                    $other     = $model->getQualifiedOwnerKeyName();
                    $deletedAt = $this->checkSoftDeletesOnModel($model->getRelated());
                    break;

                default:
                    throw new Exception('Relation ' . get_class($model) . ' is not yet supported.');
            }
            $this->performJoin($table, $foreign, $other, $deletedAt);
            $lastQuery = $model->getQuery();
        }

        return $table . '.' . $relationColumn;
    }
251066
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]);
                }
            }
        }

        if (!empty($options['json'])) {
            $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
        }

        $result = $this->model->newInstance($result, $resultSet ? null : $this->getModelUpdateCondition($options));

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

        // 输出属性控制
        if (!empty($options['visible'])) {
            $result->visible($options['visible']);
        } elseif (!empty($options['hidden'])) {
            $result->hidden($options['hidden']);
        }

        if (!empty($options['append'])) {
            $result->append($options['append']);
        }

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

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

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

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