v7.0.0-beta.2 release

This commit is contained in:
HeuJung committed 2026-04-20 20:37:49 +09:00
1 parent 6595fd0eb5
commit fa8eba7a0d
1165 files changed
+107118 -762127

No files matched your search

+76 -45
View File
@@ -3,24 +3,27 @@
namespace App\Extension;
use App\Contracts\Extension\HookManagerInterface;
use App\Events\GenericBroadcastEvent;
use App\Models\PermissionHook;
use App\Models\User;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
class HookManager implements HookManagerInterface
{
private static array $hooks = [];
private static array $filters = [];
private static array $dispatching = [];
/**
* Hook 이벤트를 발생시켜 등록된 콜백들을 실행합니다.
*
* @param string $hookName Hook 이름
* @param mixed ...$args Hook에 전달할 인수들
* @return void
* @param string $hookName Hook 이름
* @param mixed ...$args Hook에 전달할 인수들
*/
public static function doAction(string $hookName, ...$args): void
{
@@ -50,9 +53,9 @@ class HookManager implements HookManagerInterface
/**
* Filter를 적용하여 데이터를 변환합니다.
*
* @param string $filterName Filter 이름
* @param mixed $value 변환할 원본 값
* @param mixed ...$args Filter에 전달할 추가 인수들
* @param string $filterName Filter 이름
* @param mixed $value 변환할 원본 값
* @param mixed ...$args Filter에 전달할 추가 인수들
* @return mixed 변환된 값
*/
public static function applyFilters(string $filterName, $value, ...$args)
@@ -60,7 +63,7 @@ class HookManager implements HookManagerInterface
$eventName = "filter.{$filterName}";
// 등록된 필터가 있는지 확인
if (!isset(self::$filters[$filterName])) {
if (! isset(self::$filters[$filterName])) {
return $value;
}
@@ -81,18 +84,17 @@ class HookManager implements HookManagerInterface
/**
* Hook 이벤트에 콜백 함수를 등록합니다.
*
* @param string $hookName Hook 이름
* @param callable $callback 실행할 콜백 함수
* @param int $priority 실행 우선순위 (기본값: 10)
* @return void
* @param string $hookName Hook 이름
* @param callable $callback 실행할 콜백 함수
* @param int $priority 실행 우선순위 (기본값: 10)
*/
public static function addAction(string $hookName, callable $callback, int $priority = 10): void
{
if (!isset(self::$hooks[$hookName])) {
if (! isset(self::$hooks[$hookName])) {
self::$hooks[$hookName] = [];
}
if (!isset(self::$hooks[$hookName][$priority])) {
if (! isset(self::$hooks[$hookName][$priority])) {
self::$hooks[$hookName][$priority] = [];
}
@@ -100,7 +102,7 @@ class HookManager implements HookManagerInterface
// Laravel 이벤트 시스템에도 등록 (가드 래핑으로 doAction 실행 중 중복 방지)
Event::listen("hook.{$hookName}", function (...$args) use ($hookName, $callback) {
if (!empty(self::$dispatching[$hookName])) {
if (! empty(self::$dispatching[$hookName])) {
return; // doAction() 내부 배열에서 이미 실행됨 → 스킵
}
call_user_func_array($callback, $args);
@@ -110,30 +112,63 @@ class HookManager implements HookManagerInterface
/**
* Filter에 콜백 함수를 등록합니다.
*
* @param string $filterName Filter 이름
* @param callable $callback 실행할 콜백 함수
* @param int $priority 실행 우선순위 (기본값: 10)
* @return void
* @param string $filterName Filter 이름
* @param callable $callback 실행할 콜백 함수
* @param int $priority 실행 우선순위 (기본값: 10)
*/
public static function addFilter(string $filterName, callable $callback, int $priority = 10): void
{
if (!isset(self::$filters[$filterName])) {
if (! isset(self::$filters[$filterName])) {
self::$filters[$filterName] = [];
}
if (!isset(self::$filters[$filterName][$priority])) {
if (! isset(self::$filters[$filterName][$priority])) {
self::$filters[$filterName][$priority] = [];
}
self::$filters[$filterName][$priority][] = $callback;
}
/**
* WebSocket 브로드캐스트를 실행합니다.
*
* broadcast(new Event(...)) 직접 호출 대신 이 메서드를 사용합니다.
* 내부적으로 GenericBroadcastEvent를 생성하여 Laravel broadcast()를 실행합니다.
*
* @param string $channel 채널명 (예: 'admin.dashboard', 'user.notifications.123')
* @param string $eventName 이벤트명 (예: 'dashboard.stats.updated', 'notification.received')
* @param array $payload 브로드캐스트 데이터
*/
public static function broadcast(string $channel, string $eventName, array $payload = []): void
{
$driver = config('broadcasting.default');
if (in_array($driver, ['null', 'log', null], true)) {
return;
}
// Reverb/Pusher 드라이버: 호스트 미설정 시 연결 시도 없이 건너뜀
$connection = config("broadcasting.connections.{$driver}");
if (empty($connection['options']['host'] ?? null)) {
return;
}
try {
broadcast(new GenericBroadcastEvent($channel, $eventName, $payload));
} catch (\Throwable $e) {
Log::warning('브로드캐스트 실패 (Reverb 미실행 가능)', [
'channel' => $channel,
'event' => $eventName,
'error' => $e->getMessage(),
]);
}
}
/**
* Hook 이벤트에서 콜백 함수를 제거합니다.
*
* @param string $hookName Hook 이름
* @param callable $callback 제거할 콜백 함수
* @return void
* @param string $hookName Hook 이름
* @param callable $callback 제거할 콜백 함수
*/
public static function removeAction(string $hookName, callable $callback): void
{
@@ -153,9 +188,8 @@ class HookManager implements HookManagerInterface
/**
* Filter에서 콜백 함수를 제거합니다.
*
* @param string $filterName Filter 이름
* @param callable $callback 제거할 콜백 함수
* @return void
* @param string $filterName Filter 이름
* @param callable $callback 제거할 콜백 함수
*/
public static function removeFilter(string $filterName, callable $callback): void
{
@@ -192,8 +226,7 @@ class HookManager implements HookManagerInterface
/**
* 특정 Hook에 등록된 모든 콜백을 제거합니다.
*
* @param string $hookName Hook 이름
* @return void
* @param string $hookName Hook 이름
*/
public static function clearAction(string $hookName): void
{
@@ -208,8 +241,7 @@ class HookManager implements HookManagerInterface
/**
* 특정 Filter에 등록된 모든 콜백을 제거합니다.
*
* @param string $filterName Filter 이름
* @return void
* @param string $filterName Filter 이름
*/
public static function clearFilter(string $filterName): void
{
@@ -222,8 +254,6 @@ class HookManager implements HookManagerInterface
* 테스트 환경에서 등록된 모든 Hook과 Filter를 초기화합니다.
*
* 주의: 이 메서드는 테스트 환경에서만 사용해야 합니다.
*
* @return void
*/
public static function resetAll(): void
{
@@ -238,15 +268,15 @@ class HookManager implements HookManagerInterface
* permission_hooks 테이블에 해당 훅이 매핑되어 있으면
* 현재 사용자가 해당 권한을 보유하고 있는지 확인합니다.
*
* @param string $hookName 훅 이름
* @param User|null $user 사용자 (null이면 현재 인증된 사용자)
* @return void
* @param string $hookName 훅 이름
* @param User|null $user 사용자 (null이면 현재 인증된 사용자)
*
* @throws AuthorizationException 권한이 없는 경우
*/
public static function checkHookPermission(string $hookName, ?User $user = null): void
{
// 훅에 권한 매핑이 없으면 모든 사용자 허용
if (!PermissionHook::hasPermissionMapping($hookName)) {
if (! PermissionHook::hasPermissionMapping($hookName)) {
return;
}
@@ -254,7 +284,7 @@ class HookManager implements HookManagerInterface
$user = $user ?? Auth::user();
// 비인증 사용자는 권한 매핑된 훅 접근 불가
if (!$user) {
if (! $user) {
throw new AuthorizationException(__('auth.unauthorized'));
}
@@ -278,10 +308,10 @@ class HookManager implements HookManagerInterface
/**
* 훅 실행 전 권한 체크 후 Action 실행
*
* @param string $hookName 훅 이름
* @param User|null $user 권한 체크 대상 사용자
* @param mixed ...$args 훅에 전달할 인자
* @return void
* @param string $hookName 훅 이름
* @param User|null $user 권한 체크 대상 사용자
* @param mixed ...$args 훅에 전달할 인자
*
* @throws AuthorizationException 권한이 없는 경우
*/
public static function doActionWithPermission(string $hookName, ?User $user = null, ...$args): void
@@ -293,11 +323,12 @@ class HookManager implements HookManagerInterface
/**
* 훅 실행 전 권한 체크 후 Filter 실행
*
* @param string $hookName 훅 이름
* @param User|null $user 권한 체크 대상 사용자
* @param mixed $value 필터링할 값
* @param mixed ...$args 필터에 전달할 추가 인자
* @param string $hookName 훅 이름
* @param User|null $user 권한 체크 대상 사용자
* @param mixed $value 필터링할 값
* @param mixed ...$args 필터에 전달할 추가 인자
* @return mixed 필터링된 값
*
* @throws AuthorizationException 권한이 없는 경우
*/
public static function applyFiltersWithPermission(string $hookName, ?User $user = null, mixed $value = null, ...$args): mixed