공개 제보 — 파일 스토리지에서 S3 를 선택해 저장해도 실제 파일 저장이 동작하지 않던 결함의 전면 수정. - S3 어댑터(league/flysystem-aws-s3-v3)·predis 를 코어 기본 의존성으로 포함 — 어댑터 부재 즉사, phpredis 확장 없는 서버의 redis 선택 전면 다운 차단 (부트 시 확장 부재 감지 → predis 자동 폴백) - storage_driver=s3 저장 시 코어 첨부 업로드 디스크를 s3 로 전환 (ATTACHMENT_DISK env 명시가 항상 우선, 기존 행은 저장 당시 disk 로 서빙) - 첨부·템플릿 레이아웃 첨부 서빙을 행 disk 를 따르는 스토리지 스트림으로 교체 — 로컬 절대 경로 전제 fileResponse 는 S3 행에서 filemtime stat 500 (streamedFileResponse: 행 메타 기반 ETag/304/Cache-Control) - S3 호환 스토리지(R2/MinIO/NCP) 연결 지원: 엔드포인트 URL·path-style 설정 신설, 리전 목록 선택 → 자유 입력 전환, 연결 테스트를 실제 저장 경로와 동일 설정(endpoint/path-style)으로 정렬 - 사용 불능 드라이버(어댑터·PHP 확장 부재)의 저장/테스트 요청을 사유와 함께 422 로 차단하는 서버 게이트 신설 (DriverRegistryService 능력 판정) - 웹소켓 연결 테스트에 서버(백엔드 발송용) endpoint 검사 추가 — 클라이언트만 검사해 테스트 성공 + 실제 발송 실패가 가능하던 비대칭 해소 - env 빈 값(`KEY=`) 함정 정규화: AWS_URL/AWS_ENDPOINT/ATTACHMENT_DISK 빈 문자열을 미설정으로 취급 (config 정규화 + 예시 파일 주석 처리) - 플러그인 드라이버 폴백의 log 카테고리 죽은 키(logging.default) 정정 및 websocket 유령 설정 키 제거 - 실 AWS S3 종단 검증 완료 (설정 저장 → 업로드 S3 실저장 → 서빙 200/304)
6.2 KiB
Admin 환경설정 값 접근 (g7_core_settings vs config())
목적: admin UI 가 SSoT 인 환경설정 값을 코드에서 읽을 때
g7_core_settings()와config()중 무엇을 써야 하는지 결정 기준 제공
TL;DR (5초 요약)
1. 동기화 SSoT: storage/app/settings/*.json → SettingsServiceProvider::register() 가 Laravel config() 로 sync
2. sync 된 키는 config() 와 g7_core_settings() 가 동치 — 둘 중 어느 쪽을 써도 같은 값
3. 예외 1: app.timezone 은 항상 UTC (서버 저장용). 사용자 시간대는 g7_core_settings('general.timezone') 또는 config('app.default_user_timezone')
4. 예외 2: testing 환경에서 drivers.cache/session/queue/session_lifetime 은 sync 차단 (격리 보호) — config() 는 phpunit.xml 격리값, g7_core_settings() 는 dev 공유 파일값
5. 결론: 드라이버 카테고리는 config() 우선. 그 외 admin 관리 키는 둘 다 가능 (취향)
동기화 매핑 (sync 된 키)
SettingsServiceProvider::register() 가 storage/app/settings/*.json → Laravel config() 로 단방향 sync.
| g7 카테고리.키 | sync 된 Laravel config 키 |
|---|---|
mail.mailer |
mail.default |
mail.host, mail.port, mail.username, mail.password, mail.encryption |
mail.mailers.smtp.* |
mail.from_address |
mail.from.address |
mail.from_name |
mail.from.name |
mail.mailgun_* |
services.mailgun.* |
mail.ses_* |
services.ses.* |
general.site_name |
app.name |
general.site_url |
app.url |
general.timezone |
app.default_user_timezone (app.timezone 아님) |
general.language |
app.locale |
debug.mode |
app.debug, logging.*.level |
drivers.cache_driver |
cache.default (testing 차단) |
drivers.session_driver |
session.driver (testing 차단) |
drivers.session_lifetime |
session.lifetime (testing 차단) |
drivers.queue_driver |
queue.default (testing 차단) |
drivers.storage_driver |
filesystems.default |
drivers.search_engine_driver |
scout.driver |
drivers.redis_* |
database.redis.* |
drivers.memcached_* |
cache.stores.memcached.* |
drivers.s3_* |
filesystems.disks.s3.* — s3_url → url(공개 URL base), s3_endpoint → endpoint(API 요청 대상), s3_use_path_style → use_path_style_endpoint |
drivers.storage_driver = s3 |
attachment.disk = s3 (단, ATTACHMENT_DISK env 명시 시 env 우선 — attachment.disk_explicit 로 판별) |
geoip.feature_enabled, geoip.license_key, geoip.auto_update_enabled |
geoip.* |
위 매핑은 app/Providers/SettingsServiceProvider.php 가 단일 SSoT.
어느 쪽을 쓸 것인가
둘 다 동치 (자유 선택)
SettingsServiceProvider::register() 가 sync 한 키는 둘 다 같은 값을 반환한다. 가독성 기준으로 선택.
// 둘 다 동치
config('app.name'); // sync 결과
g7_core_settings('general.site_name'); // SSoT 직접 조회
config('mail.from.address');
g7_core_settings('mail.from_address');
config('cache.default');
g7_core_settings('drivers.cache_driver');
config() 를 써야 하는 경우 (testing 격리)
drivers.cache_driver / drivers.session_driver / drivers.queue_driver / drivers.session_lifetime 는 testing 환경에서 sync 가 차단된다 (이슈 #258 회귀 방지 — storage/app/settings/drivers.json 이 dev 와 testing 이 공유하는 파일이므로 dev 의 Redis/DB 드라이버가 testing 으로 흘러들면 격리가 깨진다).
// testing 환경에서:
config('queue.default'); // phpunit.xml 의 sync (격리 유지)
g7_core_settings('drivers.queue_driver'); // dev 공유 drivers.json 의 database (격리 깨짐)
따라서 드라이버 카테고리는 config() 사용이 안전하다. 새 코드에서 g7_core_settings('drivers.*') 직접 조회는 테스트 격리 회귀 위험을 만든다.
g7_core_settings() 만 정확한 경우 (의도적으로 분리된 키)
general.timezone ↔ app.timezone 은 의도적으로 다르다.
| 키 | 의미 | 값 예 |
|---|---|---|
config('app.timezone') |
서버 저장 타임존 (변경 금지) | UTC 고정 |
config('app.default_user_timezone') |
사용자 표시용 (sync 됨) | Asia/Seoul |
g7_core_settings('general.timezone') |
사용자 표시용 (SSoT) | Asia/Seoul |
사용자에게 표시할 타임존이 필요하면 config('app.default_user_timezone') 또는 g7_core_settings('general.timezone') 을 사용한다. config('app.timezone') 사용은 항상 UTC 가 반환되므로 사용자 표시 의도라면 버그다.
새 admin 환경설정 키 추가 시 점검
새 카테고리/키를 storage/app/settings/*.json 에 추가할 때:
SettingsServiceProvider::applyXxxConfig()에 sync 코드를 추가하면 →config()/g7_core_settings()둘 다 동치.- sync 코드를 추가하지 않으면 →
g7_core_settings()만 사용 가능.config()는 sync 되지 않은 키를 모르기 때문이다. - testing 격리가 필요한 키 (드라이버, 외부 서비스 자격증명 등) 는
! $isTestingEnv가드로 sync 를 차단한다. 이 키들은config()가 testing 격리 SSoT 다. - 의미가 다른 키 (
app.timezone처럼) 는 sync 하지 않고 별도 키 (app.default_user_timezone) 로 분리한다. - 고급 탭 화면에 얹을 카테고리는
config/settings/defaults.json의frontend_schema.{카테고리}.merge_into를advanced로 선언한다. 저장 시 어느 카테고리 파일에 쓸지는 이 선언에서 도출되므로 별도 등록이 필요 없다. 선언이 없으면 화면·검증·읽기가 모두 정상인데 입력값만 저장되지 않고 버려진다 — 저장 응답은 성공이고 화면에도 값이 보여 실패 신호가 없으므로, 새 카테고리를 추가했으면 저장 후storage/app/settings/{카테고리}.json이 생성되는지 직접 확인한다.
관련 문서
- service-provider.md — ServiceProvider 안전성 (DB 접근 가드)
- core-config.md —
config/core.php(별도 SSoT, admin 환경설정과 무관)