treePathModelClass) || !is_string($this->treePathModelClass)) { throw new InvalidConfigException('在ClosureTableBehavior中没有配置树路径表名'); } parent::attach($owner); } /** * @inheritDoc */ public function events(): array { return [ ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert', ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeUpdate', ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate', ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete', ]; } /** * 新增数据重新计算路径 * * @throws InvalidConfigException */ public function afterInsert(): void { if ($this->owner->getAttribute($this->ownerParentIdAttribute)) { $this->addTreePathOwnerToParents(); } $this->addTreePathOwnerToOwner(); } /** * 创建父子关系. * * @throws InvalidConfigException */ protected function addTreePathOwnerToParents(): void { foreach ($this->getTreePathParents() as $treePath) { $this->saveTreePathModel( $treePath->parent_id, $this->owner->id, $this->owner->getAttribute($this->ownerParentIdAttribute), $treePath->parent_level, $treePath->child_level + 1 ); } } /** * 返回节点树路径关系 * * @return array */ protected function getTreePathParents(): array { $primaryKey = $this->owner->getAttribute($this->ownerParentIdAttribute); if ($primaryKey === 0) { return []; } return $this->treePathModelClass::find() ->byChildId($primaryKey) ->orderBy('child_level') ->all(); } /** * 保存树路径 * * @param int $parentId * @param int $childId * @param int|null $nearestParentId * @param int $parentLevel * @param int $childLevel * @return bool * @throws InvalidConfigException */ protected function saveTreePathModel( int $parentId, int $childId, ?int $nearestParentId, ?int $parentLevel, int $childLevel ): bool { $model = $this->addTreePathModelObject(); $model->parent_id = $parentId; $model->child_id = $childId; $model->nearest_parent_id = $nearestParentId; $model->parent_level = $parentLevel; $model->child_level = $childLevel; return $model->save(); } /** * 创建树路径模型对象 * * @return AbstractTreePath * @throws InvalidConfigException */ protected function addTreePathModelObject(): AbstractTreePath { return Yii::createObject($this->treePathModelClass); } /** * 创建 owner-owner 关系. * * @throws InvalidConfigException */ protected function addTreePathOwnerToOwner(): void { $parentLevel = $this->getParentLevel(); $this->saveTreePathModel( $this->owner->id, $this->owner->id, $this->owner->getAttribute($this->ownerParentIdAttribute), $parentLevel + 1, $parentLevel + 1 ); } /** * 返回父级别 * * @return int */ protected function getParentLevel(): ?int { if (empty($this->owner->getAttribute($this->ownerParentIdAttribute))) { return 0; } return $this->treePathModelClass::find() ->select('parent_level') ->byParentId($this->owner->getAttribute($this->ownerParentIdAttribute)) ->byChildId($this->owner->getAttribute($this->ownerParentIdAttribute)) ->scalar(); } /** * 更新之前验证 * * @throws LogicException */ public function beforeUpdate(): void { $this->oldParentId = $this->owner->oldAttributes[$this->ownerParentIdAttribute] ?? 0; if ($this->owner->getAttribute($this->ownerParentIdAttribute) !== 0) { if ($this->hasChilds()) { throw new LogicException('你不能把parent移动到child下边'); } } } /** * 验证父节点下是否有子节点 * * @return bool */ private function hasChilds(): bool { return (bool)$this->treePathModelClass::find() ->byParentId($this->owner->id) ->byChildId($this->owner->getAttribute($this->ownerParentIdAttribute)) ->count(); } /** * 删除之前检查父节点 * * @throws LogicException */ public function beforeDelete(): void { switch ($this->deletionType) { case self::DELETION_TYPE_1: $this->deletionType1(); break; case self::DELETION_TYPE_0: default: $this->deletionType0(); break; } } /** * 如果有子节点,删除时不会删除当前节点 */ protected function deletionType0(): void { if ($this->childs()->count()) { throw new LogicException('你不能删除包含子集的元素'); } $this->removeTreePathByIds($this->owner->id); } /** * 移除元素,首先移动在元素的父节点下的子节点 */ protected function deletionType1(): void { foreach ($this->childs(1)->all() as $child) { $child->setAttribute( $this->ownerParentIdAttribute, $this->owner->getAttribute($this->ownerParentIdAttribute) ); $child->save(); } $this->removeTreePathByIds($this->owner->id); } /** * 在更新父节点后更新树路径 */ public function afterUpdate(): void { if ($this->oldParentId != $this->owner->getAttribute($this->ownerParentIdAttribute)) { $this->rebuildTreePath(); } } /** * 获取元素所有子节点 * * @param bool|false $withParent * @param int|null $depth * @param bool|false $eagerLoading * @return ActiveQuery */ public function childs(?int $depth = null, bool $withParent = false, bool $eagerLoading = false): ActiveQuery { return $this->owner::find() ->childs($this->owner->id, $withParent, $depth, $eagerLoading) ->orderBy(['treePathsChild.child_level' => SORT_ASC]); } /** * 添加树路径 * * @throws InvalidConfigException */ public function addTreePath(): void { $this->addTreePathOwnerToParents(); $this->addTreePathOwnerToOwner(); } /** * 重建树路径 * * @throws InvalidConfigException */ public function rebuildTreePath(): void { $childs = $this->childs()->all(); $this->removeTreePathByIds($this->owner->id); $this->addTreePath(); if ($childs) { $this->removeTreePathByIds(ArrayHelper::map($childs, 'id', 'id')); foreach ($childs as $child) { $child->addTreePath(); } } } /** * 删除树路径 by ids */ /** * @param array|int $ids */ protected function removeTreePathByIds($ids): void { $this->treePathModelClass::deleteAll( [ 'child_id' => $ids, ] ); } }