Laravel:递归计算 eloquent 关系的 parents 的计数(获取关系深度)
Laravel: Recursively compute count of parents of an eloquent relationship (get the relationship depth)
考虑下面的节点图:
如上图所示,我想创建一个函数来告诉我 top-level 节点的关系深度。
实例模型
/**
* Get the immediate parent instance of the instance.
*/
public function parent()
{
return $this->hasOne(Instance::class);
}
/**
* Get the children instances of the instance.
*/
public function children()
{
return $this->hasMany(Instance::class);
}
/**
* Get the depth of an instance.
* @return int
*/
public function getDepthAttribute()
{
// TODO
}
实例Table
Schema::create('instances', function (Blueprint $table) {
$table->id();
$table->string('name', 32);
$table->foreignId('instance_id')->nullable();
$table->timestamps();
});
Schema::table('instances', function (Blueprint $table)
{
$table->foreign('instance_id')->references('id')->on('instances')->onUpdate('cascade')->onDelete('cascade');
});
实例table例子
id name instance_id | (getDepthAttribute() should return)
-------------------------- |
1 A NULL | 0
2 B 1 | 1
3 C 2 | 2
4 D 3 | 3
5 E 3 | 3
6 F 3 | 3
一句话,我的问题是:“如果一个实例有一个parent实例,加1。重复直到parent实例没有一个parent实例。然后return 最终值。"
如何在 Laravel 中正确完成此操作?
我的解决方案没有使用递归,而且我不知道它在处理大数据时的表现如何。我认为这不会成为问题,但我建议您播种巨大的 table 并衡量性能。我的解决方案是创建一个深度图数组。
public $depthMap = [];
public $data = [
['id' => 1, 'name' => 'a', 'iid' => null],
['id' => 2, 'name' => 'b', 'iid' => 1],
['id' => 3, 'name' => 'c', 'iid' => 2],
['id' => 4, 'name' => 'd', 'iid' => 2],
['id' => 5, 'name' => 'e', 'iid' => 3],
['id' => 6, 'name' => 'f', 'iid' => 4],
['id' => 7, 'name' => 'g', 'iid' => 3],
['id' => 8, 'name' => 'h', 'iid' => 5],
['id' => 9, 'name' => 'i', 'iid' => null],
['id' => 10, 'name' => 'j', 'iid' => 7],
['id' => 11, 'name' => 'k', 'iid' => 9],
['id' => 12, 'name' => 'l', 'iid' => 10],
['id' => 13, 'name' => 'm', 'iid' => 4],
['id' => 14, 'name' => 'n', 'iid' => 3],
['id' => 15, 'name' => 'o', 'iid' => 12],
['id' => 16, 'name' => 'p', 'iid' => 10],
];
public function handle()
{
foreach ($this->data as $item) {
$this->depthMap[] = trim($this->getDepth($item) . ".{$item['id']}", '.');
}
}
public function getDepth($item)
{
foreach (array_reverse($this->depthMap) as $mapItem) {
if (array_reverse(explode('.', $mapItem))[0] == $item['iid']) return $mapItem;
}
return '';
}
/* The output
[
"1",
"1.2",
"1.2.3",
"1.2.4",
"1.2.3.5",
"1.2.4.6",
"1.2.3.7",
"1.2.3.5.8",
"9",
"1.2.3.7.10",
"9.11",
"1.2.3.7.10.12",
"1.2.4.13",
"1.2.3.14",
"1.2.3.7.10.12.15",
"1.2.3.7.10.16",
];
*/
现在,你可以对explode(), array_reverse(), count() 操作,现在不仅是深度的长度,还有整个关系图。
以下是我想出的解决方案:
DepthHelper.php
use \App\Models\Instance;
/**
* Returns the depth of an Instance
* @param $idToFind
* @return int
*/
function DepthHelper($idToFind){
return GetParentHelper($idToFind);
}
// Recursive Helper function
function GetParentHelper($id, $depth = 0) {
$model = Instance::find($id);
if ($model->instance_id != null) {
$depth++;
return GetParentHelper($model->instance_id, $depth);
} else {
return $depth;
}
}
实例模型
/**
* Get the depth of this instance from the top-level instance.
*/
public function getDepthAttribute()
{
return DepthHelper($this->id);
}
protected array $appends = ['depth'];
考虑下面的节点图:
如上图所示,我想创建一个函数来告诉我 top-level 节点的关系深度。
实例模型
/**
* Get the immediate parent instance of the instance.
*/
public function parent()
{
return $this->hasOne(Instance::class);
}
/**
* Get the children instances of the instance.
*/
public function children()
{
return $this->hasMany(Instance::class);
}
/**
* Get the depth of an instance.
* @return int
*/
public function getDepthAttribute()
{
// TODO
}
实例Table
Schema::create('instances', function (Blueprint $table) {
$table->id();
$table->string('name', 32);
$table->foreignId('instance_id')->nullable();
$table->timestamps();
});
Schema::table('instances', function (Blueprint $table)
{
$table->foreign('instance_id')->references('id')->on('instances')->onUpdate('cascade')->onDelete('cascade');
});
实例table例子
id name instance_id | (getDepthAttribute() should return)
-------------------------- |
1 A NULL | 0
2 B 1 | 1
3 C 2 | 2
4 D 3 | 3
5 E 3 | 3
6 F 3 | 3
一句话,我的问题是:“如果一个实例有一个parent实例,加1。重复直到parent实例没有一个parent实例。然后return 最终值。"
如何在 Laravel 中正确完成此操作?
我的解决方案没有使用递归,而且我不知道它在处理大数据时的表现如何。我认为这不会成为问题,但我建议您播种巨大的 table 并衡量性能。我的解决方案是创建一个深度图数组。
public $depthMap = [];
public $data = [
['id' => 1, 'name' => 'a', 'iid' => null],
['id' => 2, 'name' => 'b', 'iid' => 1],
['id' => 3, 'name' => 'c', 'iid' => 2],
['id' => 4, 'name' => 'd', 'iid' => 2],
['id' => 5, 'name' => 'e', 'iid' => 3],
['id' => 6, 'name' => 'f', 'iid' => 4],
['id' => 7, 'name' => 'g', 'iid' => 3],
['id' => 8, 'name' => 'h', 'iid' => 5],
['id' => 9, 'name' => 'i', 'iid' => null],
['id' => 10, 'name' => 'j', 'iid' => 7],
['id' => 11, 'name' => 'k', 'iid' => 9],
['id' => 12, 'name' => 'l', 'iid' => 10],
['id' => 13, 'name' => 'm', 'iid' => 4],
['id' => 14, 'name' => 'n', 'iid' => 3],
['id' => 15, 'name' => 'o', 'iid' => 12],
['id' => 16, 'name' => 'p', 'iid' => 10],
];
public function handle()
{
foreach ($this->data as $item) {
$this->depthMap[] = trim($this->getDepth($item) . ".{$item['id']}", '.');
}
}
public function getDepth($item)
{
foreach (array_reverse($this->depthMap) as $mapItem) {
if (array_reverse(explode('.', $mapItem))[0] == $item['iid']) return $mapItem;
}
return '';
}
/* The output
[
"1",
"1.2",
"1.2.3",
"1.2.4",
"1.2.3.5",
"1.2.4.6",
"1.2.3.7",
"1.2.3.5.8",
"9",
"1.2.3.7.10",
"9.11",
"1.2.3.7.10.12",
"1.2.4.13",
"1.2.3.14",
"1.2.3.7.10.12.15",
"1.2.3.7.10.16",
];
*/
现在,你可以对explode(), array_reverse(), count() 操作,现在不仅是深度的长度,还有整个关系图。
以下是我想出的解决方案:
DepthHelper.php
use \App\Models\Instance;
/**
* Returns the depth of an Instance
* @param $idToFind
* @return int
*/
function DepthHelper($idToFind){
return GetParentHelper($idToFind);
}
// Recursive Helper function
function GetParentHelper($id, $depth = 0) {
$model = Instance::find($id);
if ($model->instance_id != null) {
$depth++;
return GetParentHelper($model->instance_id, $depth);
} else {
return $depth;
}
}
实例模型
/**
* Get the depth of this instance from the top-level instance.
*/
public function getDepthAttribute()
{
return DepthHelper($this->id);
}
protected array $appends = ['depth'];