Skip to content

Commit 90b49dc

Browse files
author
masaton0216
committed
improve: ユーザ管理 項目詳細設定 登録時、循環参照チェックを追加
1 parent aa592dd commit 90b49dc

File tree

3 files changed

+379
-0
lines changed

3 files changed

+379
-0
lines changed

app/Plugins/Manage/UserManage/UserManage.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3032,6 +3032,10 @@ public function updateColumnDetail($request, $id)
30323032
if ($trigger_column->columns_set_id != $column->columns_set_id) {
30333033
$fail('トリガーとなる項目は同じ項目セットに属している必要があります。');
30343034
}
3035+
// 循環依存チェック(A→B→C→Aのような循環参照を防止)
3036+
if (UsersTool::hasCyclicDependency($column->id, $value, $column->columns_set_id)) {
3037+
$fail('この設定により循環依存が発生します。トリガーとなる項目の依存関係を確認してください。');
3038+
}
30353039
}
30363040
}
30373041
};

app/Plugins/Manage/UserManage/UsersTool.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,4 +463,63 @@ public static function getConditionalDisplaySettings($columns_set_id)
463463

464464
return $settings;
465465
}
466+
467+
/**
468+
* 循環依存をチェックする
469+
*
470+
* 指定された項目をトリガーに設定した場合、循環依存が発生しないかをチェックします。
471+
* 例: A→B→C→A のような循環参照を検出
472+
*
473+
* @param int $column_id 条件付き表示を設定する項目のID
474+
* @param int $trigger_column_id トリガーとして設定しようとしている項目のID
475+
* @param int $columns_set_id 項目セットID
476+
* @return bool 循環依存がある場合true、ない場合false
477+
*/
478+
public static function hasCyclicDependency($column_id, $trigger_column_id, $columns_set_id)
479+
{
480+
// トリガー項目が設定されていない場合は循環しない
481+
if (empty($trigger_column_id)) {
482+
return false;
483+
}
484+
485+
// 訪問済みノードを記録(無限ループ防止)
486+
$visited = [];
487+
488+
// 探索スタック(深さ優先探索)
489+
$stack = [$trigger_column_id];
490+
491+
// 同一項目セット内の条件付き表示設定を一度に取得(パフォーマンス最適化)
492+
$conditional_columns = UsersColumns::where('columns_set_id', $columns_set_id)
493+
->where('conditional_display_flag', ShowType::show)
494+
->whereNotNull('conditional_trigger_column_id')
495+
->get()
496+
->keyBy('id');
497+
498+
while (!empty($stack)) {
499+
$current_id = array_pop($stack);
500+
501+
// 自分自身に到達したら循環依存を検出
502+
if ($current_id == $column_id) {
503+
return true;
504+
}
505+
506+
// 既に訪問済みの場合はスキップ
507+
if (in_array($current_id, $visited)) {
508+
continue;
509+
}
510+
511+
// 訪問済みとしてマーク
512+
$visited[] = $current_id;
513+
514+
// 現在のノードがトリガーとして設定されているか確認
515+
$current_column = $conditional_columns->get($current_id);
516+
if ($current_column && $current_column->conditional_trigger_column_id) {
517+
// 次のトリガーをスタックに追加
518+
$stack[] = $current_column->conditional_trigger_column_id;
519+
}
520+
}
521+
522+
// 循環依存なし
523+
return false;
524+
}
466525
}

tests/Unit/Plugins/Manage/UserManage/UsersToolTest.php

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,4 +1027,320 @@ public function testGetConditionalDisplaySettingsWhenTriggerColumnDeleted()
10271027
$this->assertCount(1, $settings);
10281028
$this->assertNull($settings[0]['trigger_column_type']);
10291029
}
1030+
1031+
/**
1032+
* hasCyclicDependency: 循環依存がない場合はfalseを返す
1033+
*
1034+
* @test
1035+
*/
1036+
public function testHasCyclicDependencyReturnsFalseWhenNoCycle()
1037+
{
1038+
// テスト用ユーザーを作成
1039+
$user = User::factory()->create();
1040+
1041+
// 項目セットを作成
1042+
$columns_set = UsersColumnsSet::create([
1043+
'name' => 'テスト項目セット',
1044+
'display_sequence' => 1,
1045+
'created_id' => $user->id,
1046+
'updated_id' => $user->id,
1047+
]);
1048+
1049+
// 項目A、B、Cを作成(A→B→Cの依存関係)
1050+
$column_a = UsersColumns::create([
1051+
'columns_set_id' => $columns_set->id,
1052+
'column_type' => UserColumnType::text,
1053+
'column_name' => '項目A',
1054+
'required' => Required::off,
1055+
'display_sequence' => 1,
1056+
'created_id' => $user->id,
1057+
'updated_id' => $user->id,
1058+
]);
1059+
1060+
$column_b = UsersColumns::create([
1061+
'columns_set_id' => $columns_set->id,
1062+
'column_type' => UserColumnType::text,
1063+
'column_name' => '項目B',
1064+
'required' => Required::off,
1065+
'conditional_display_flag' => ShowType::show,
1066+
'conditional_trigger_column_id' => $column_a->id,
1067+
'conditional_operator' => ConditionalOperator::equals,
1068+
'conditional_value' => 'test',
1069+
'display_sequence' => 2,
1070+
'created_id' => $user->id,
1071+
'updated_id' => $user->id,
1072+
]);
1073+
1074+
$column_c = UsersColumns::create([
1075+
'columns_set_id' => $columns_set->id,
1076+
'column_type' => UserColumnType::text,
1077+
'column_name' => '項目C',
1078+
'required' => Required::off,
1079+
'conditional_display_flag' => ShowType::show,
1080+
'conditional_trigger_column_id' => $column_b->id,
1081+
'conditional_operator' => ConditionalOperator::equals,
1082+
'conditional_value' => 'test',
1083+
'display_sequence' => 3,
1084+
'created_id' => $user->id,
1085+
'updated_id' => $user->id,
1086+
]);
1087+
1088+
// テスト実行: Aのトリガーとして新しい項目Dを設定しても循環しない
1089+
$has_cycle = UsersTool::hasCyclicDependency($column_a->id, 999, $columns_set->id);
1090+
1091+
// 検証: 循環依存なし
1092+
$this->assertFalse($has_cycle);
1093+
}
1094+
1095+
/**
1096+
* hasCyclicDependency: 直接的な循環依存を検出する(A→B→A)
1097+
*
1098+
* @test
1099+
*/
1100+
public function testHasCyclicDependencyDetectsDirectCycle()
1101+
{
1102+
// テスト用ユーザーを作成
1103+
$user = User::factory()->create();
1104+
1105+
// 項目セットを作成
1106+
$columns_set = UsersColumnsSet::create([
1107+
'name' => 'テスト項目セット',
1108+
'display_sequence' => 1,
1109+
'created_id' => $user->id,
1110+
'updated_id' => $user->id,
1111+
]);
1112+
1113+
// 項目A、Bを作成
1114+
$column_a = UsersColumns::create([
1115+
'columns_set_id' => $columns_set->id,
1116+
'column_type' => UserColumnType::text,
1117+
'column_name' => '項目A',
1118+
'required' => Required::off,
1119+
'display_sequence' => 1,
1120+
'created_id' => $user->id,
1121+
'updated_id' => $user->id,
1122+
]);
1123+
1124+
$column_b = UsersColumns::create([
1125+
'columns_set_id' => $columns_set->id,
1126+
'column_type' => UserColumnType::text,
1127+
'column_name' => '項目B',
1128+
'required' => Required::off,
1129+
'conditional_display_flag' => ShowType::show,
1130+
'conditional_trigger_column_id' => $column_a->id,
1131+
'conditional_operator' => ConditionalOperator::equals,
1132+
'conditional_value' => 'test',
1133+
'display_sequence' => 2,
1134+
'created_id' => $user->id,
1135+
'updated_id' => $user->id,
1136+
]);
1137+
1138+
// テスト実行: AのトリガーとしてBを設定すると循環する(A→B→A)
1139+
$has_cycle = UsersTool::hasCyclicDependency($column_a->id, $column_b->id, $columns_set->id);
1140+
1141+
// 検証: 循環依存あり
1142+
$this->assertTrue($has_cycle);
1143+
}
1144+
1145+
/**
1146+
* hasCyclicDependency: 間接的な循環依存を検出する(A→B→C→A)
1147+
*
1148+
* @test
1149+
*/
1150+
public function testHasCyclicDependencyDetectsIndirectCycle()
1151+
{
1152+
// テスト用ユーザーを作成
1153+
$user = User::factory()->create();
1154+
1155+
// 項目セットを作成
1156+
$columns_set = UsersColumnsSet::create([
1157+
'name' => 'テスト項目セット',
1158+
'display_sequence' => 1,
1159+
'created_id' => $user->id,
1160+
'updated_id' => $user->id,
1161+
]);
1162+
1163+
// 項目A、B、Cを作成(B→C、C→Aと設定)
1164+
$column_a = UsersColumns::create([
1165+
'columns_set_id' => $columns_set->id,
1166+
'column_type' => UserColumnType::text,
1167+
'column_name' => '項目A',
1168+
'required' => Required::off,
1169+
'display_sequence' => 1,
1170+
'created_id' => $user->id,
1171+
'updated_id' => $user->id,
1172+
]);
1173+
1174+
$column_b = UsersColumns::create([
1175+
'columns_set_id' => $columns_set->id,
1176+
'column_type' => UserColumnType::text,
1177+
'column_name' => '項目B',
1178+
'required' => Required::off,
1179+
'display_sequence' => 2,
1180+
'created_id' => $user->id,
1181+
'updated_id' => $user->id,
1182+
]);
1183+
1184+
$column_c = UsersColumns::create([
1185+
'columns_set_id' => $columns_set->id,
1186+
'column_type' => UserColumnType::text,
1187+
'column_name' => '項目C',
1188+
'required' => Required::off,
1189+
'conditional_display_flag' => ShowType::show,
1190+
'conditional_trigger_column_id' => $column_b->id,
1191+
'conditional_operator' => ConditionalOperator::equals,
1192+
'conditional_value' => 'test',
1193+
'display_sequence' => 3,
1194+
'created_id' => $user->id,
1195+
'updated_id' => $user->id,
1196+
]);
1197+
1198+
// Cのトリガーとして、さらにAを設定
1199+
$column_a->update([
1200+
'conditional_display_flag' => ShowType::show,
1201+
'conditional_trigger_column_id' => $column_c->id,
1202+
'conditional_operator' => ConditionalOperator::equals,
1203+
'conditional_value' => 'test',
1204+
]);
1205+
1206+
// テスト実行: BのトリガーとしてAを設定すると循環する(B→A→C→B)
1207+
$has_cycle = UsersTool::hasCyclicDependency($column_b->id, $column_a->id, $columns_set->id);
1208+
1209+
// 検証: 循環依存あり
1210+
$this->assertTrue($has_cycle);
1211+
}
1212+
1213+
/**
1214+
* hasCyclicDependency: トリガーが未設定の場合はfalseを返す
1215+
*
1216+
* @test
1217+
*/
1218+
public function testHasCyclicDependencyReturnsFalseWhenNoTrigger()
1219+
{
1220+
// テスト用ユーザーを作成
1221+
$user = User::factory()->create();
1222+
1223+
// 項目セットを作成
1224+
$columns_set = UsersColumnsSet::create([
1225+
'name' => 'テスト項目セット',
1226+
'display_sequence' => 1,
1227+
'created_id' => $user->id,
1228+
'updated_id' => $user->id,
1229+
]);
1230+
1231+
// 項目Aを作成
1232+
$column_a = UsersColumns::create([
1233+
'columns_set_id' => $columns_set->id,
1234+
'column_type' => UserColumnType::text,
1235+
'column_name' => '項目A',
1236+
'required' => Required::off,
1237+
'display_sequence' => 1,
1238+
'created_id' => $user->id,
1239+
'updated_id' => $user->id,
1240+
]);
1241+
1242+
// テスト実行: トリガーがnullの場合
1243+
$has_cycle = UsersTool::hasCyclicDependency($column_a->id, null, $columns_set->id);
1244+
1245+
// 検証: 循環依存なし
1246+
$this->assertFalse($has_cycle);
1247+
}
1248+
1249+
/**
1250+
* hasCyclicDependency: 複雑な依存関係でも循環を正しく検出する
1251+
*
1252+
* @test
1253+
*/
1254+
public function testHasCyclicDependencyDetectsComplexCycle()
1255+
{
1256+
// テスト用ユーザーを作成
1257+
$user = User::factory()->create();
1258+
1259+
// 項目セットを作成
1260+
$columns_set = UsersColumnsSet::create([
1261+
'name' => 'テスト項目セット',
1262+
'display_sequence' => 1,
1263+
'created_id' => $user->id,
1264+
'updated_id' => $user->id,
1265+
]);
1266+
1267+
// 項目A, B, C, D, Eを作成(A→B→D、C→D、D→Eの依存関係)
1268+
$column_a = UsersColumns::create([
1269+
'columns_set_id' => $columns_set->id,
1270+
'column_type' => UserColumnType::text,
1271+
'column_name' => '項目A',
1272+
'required' => Required::off,
1273+
'display_sequence' => 1,
1274+
'created_id' => $user->id,
1275+
'updated_id' => $user->id,
1276+
]);
1277+
1278+
$column_b = UsersColumns::create([
1279+
'columns_set_id' => $columns_set->id,
1280+
'column_type' => UserColumnType::text,
1281+
'column_name' => '項目B',
1282+
'required' => Required::off,
1283+
'conditional_display_flag' => ShowType::show,
1284+
'conditional_trigger_column_id' => $column_a->id,
1285+
'conditional_operator' => ConditionalOperator::equals,
1286+
'conditional_value' => 'test',
1287+
'display_sequence' => 2,
1288+
'created_id' => $user->id,
1289+
'updated_id' => $user->id,
1290+
]);
1291+
1292+
$column_c = UsersColumns::create([
1293+
'columns_set_id' => $columns_set->id,
1294+
'column_type' => UserColumnType::text,
1295+
'column_name' => '項目C',
1296+
'required' => Required::off,
1297+
'display_sequence' => 3,
1298+
'created_id' => $user->id,
1299+
'updated_id' => $user->id,
1300+
]);
1301+
1302+
$column_d = UsersColumns::create([
1303+
'columns_set_id' => $columns_set->id,
1304+
'column_type' => UserColumnType::text,
1305+
'column_name' => '項目D',
1306+
'required' => Required::off,
1307+
'conditional_display_flag' => ShowType::show,
1308+
'conditional_trigger_column_id' => $column_b->id,
1309+
'conditional_operator' => ConditionalOperator::equals,
1310+
'conditional_value' => 'test',
1311+
'display_sequence' => 4,
1312+
'created_id' => $user->id,
1313+
'updated_id' => $user->id,
1314+
]);
1315+
1316+
$column_e = UsersColumns::create([
1317+
'columns_set_id' => $columns_set->id,
1318+
'column_type' => UserColumnType::text,
1319+
'column_name' => '項目E',
1320+
'required' => Required::off,
1321+
'conditional_display_flag' => ShowType::show,
1322+
'conditional_trigger_column_id' => $column_d->id,
1323+
'conditional_operator' => ConditionalOperator::equals,
1324+
'conditional_value' => 'test',
1325+
'display_sequence' => 5,
1326+
'created_id' => $user->id,
1327+
'updated_id' => $user->id,
1328+
]);
1329+
1330+
// Cのトリガーとして、Dを設定(C→D)
1331+
$column_c->update([
1332+
'conditional_display_flag' => ShowType::show,
1333+
'conditional_trigger_column_id' => $column_d->id,
1334+
'conditional_operator' => ConditionalOperator::equals,
1335+
'conditional_value' => 'test',
1336+
]);
1337+
1338+
// テスト実行1: EのトリガーとしてCを設定しても循環しない(E→C→D→B→Aで終わり)
1339+
$has_cycle1 = UsersTool::hasCyclicDependency($column_e->id, $column_c->id, $columns_set->id);
1340+
$this->assertFalse($has_cycle1);
1341+
1342+
// テスト実行2: AのトリガーとしてEを設定すると循環する(A→E→D→B→A)
1343+
$has_cycle2 = UsersTool::hasCyclicDependency($column_a->id, $column_e->id, $columns_set->id);
1344+
$this->assertTrue($has_cycle2);
1345+
}
10301346
}

0 commit comments

Comments
 (0)