acme_tracking:AC 같은 문자열 규칙을 제공하려면 유효성 검사 Factory에 검사 처리를 등록합니다. 사용하는 애플리케이션의 폼 리퀘스트에서 내장 규칙과 같은 표기법으로 사용할 수 있습니다.
이 페이지는 Laravel Framework v13.40.0의 공식 구현을 확인한 내용입니다. 커스텀 유효성 검사 규칙의 기본 예제를 보완하여, 패키지의 공개 API로 유지보수할 때의 등록·번역·상태의 수명을 다룹니다.
규칙 객체와 구분해서 사용하기
공식 문서에서 소개하는ValidationRule 객체는 패키지에서도 사용할 수 있습니다. 문자열로 지정할 수 있는 것 자체가 요구 사항이 아니라면, 생성자에서 타입이 지정된 인수를 받을 수 있는 객체를 검토합니다.
같은
validate()라는 이름이라도 호출 규약이 다릅니다. ValidationRule 구현 클래스를 그대로 Factory::extend()의 클래스 문자열로 전달하면 세 번째 인수의 타입이나 반환값이 일치하지 않습니다.
등록·생성·검사를 분리하기
Validator 파사드의 extend()는 Illuminate\Validation\Factory에 대한 호출입니다. 입력을 검사하는 Illuminate\Validation\Validator 인스턴스와는 다른 객체입니다.
그림은 문자열 규칙이 실행되는 경우의 경로입니다. 미입력이나 nullable 등에 의한 건너뛰기는 뒤에서 설명합니다.
Factory는 validator라는 키의 singleton으로 등록되지만, 표준 구현의 make()가 반환하는 Validator는 매번 새로 생성됩니다. Factory에 나중에 등록해도 이미 생성된 Validator의 확장 배열은 갱신되지 않습니다.
패키지 고유의 규칙 등록하기
예로, 영문 대문자 접두사·하이픈·8자리 숫자로 이루어진 가상의 추적 코드를 검사합니다.AC-00000001 같은 형식의 예이며, 실제 배송 회사의 번호 사양이 아닙니다.
패키지의 네임스페이스 Acme\Courier\가 Composer의 PSR-4로 src/에 대응하고, 일반적인 비지연 Service Provider가 로드되는 구성을 가정합니다. 프로바이더의 로드는 패키지 자동 검출을 참조하세요.
검사 처리를 클래스로 분리하기
src/Validation/TrackingCode.php에 문자열 규칙용 핸들러를 준비합니다. 이 클래스는 ValidationRule 인터페이스를 구현하지 않습니다.
acme_tracking:AC의 경우 파라미터 배열은 ['AC']입니다. 규칙 쪽에서도 값의 타입을 확인하여 단독으로 사용되어도 타입 오류가 나지 않도록 하고 있습니다.
잘못된 접두사나 파라미터 개수는 사용자의 입력 오류가 아니라 규칙 정의의 설정 실수로 보고 예외를 던집니다. 빈 값으로 규칙이 건너뛰어지는 경우에는 이 설정 확인도 실행되지 않으므로, 정상적인 비어 있지 않은 값을 사용한 테스트도 필요합니다.
Factory가 해석된 후에 등록하기
src/CourierServiceProvider.php의 boot()에서 규칙과 메시지 치환 처리를 등록합니다.
callAfterResolving()은 아직 해석되지 않았다면 생성을 기다리고, 이미 해석된 Factory에도 즉시 등록합니다. 훅 자체에 대한 자세한 내용은 패키지의 서비스 해석 후 훅에서 설명합니다.
클래스 문자열 TrackingCode::class.'@validate'는 검사 시 컨테이너에서 클래스를 해석하여 메서드를 호출합니다. @validate를 생략한 클래스 이름만으로도 기본 메서드 이름은 validate입니다. 생성자 의존성이 있는 경우에는 컨테이너에서 해석할 수 있도록 바인딩합니다.
이 예제에는 singleton 핸들러 등록이나 입력을 저장하는 프로퍼티를 두지 않았습니다. 검사 시의 데이터는 인수로 다루고, Factory가 보유하는 클로저에 요청 고유의 상태를 담지 않는 설계로 합니다.
사용하는 애플리케이션에서 지정하기
bail은 해당 속성의 앞선 규칙이 실패하면 이후 규칙을 중단합니다. 필수 여부는 required, 형식은 acme_tracking처럼 역할을 나눕니다.
공개하는 이름은 acme_tracking 같은 고유한 snake case로 통일합니다. 같은 종류의 확장에 같은 이름으로 등록하면 나중 처리가 덮어씁니다. 또한 내장 validateRequired() 등이 존재하는 경우에는 그 메서드가 직접 호출되므로, 같은 이름의 확장으로 내장 검사를 대체할 수 없습니다.
사용자의 오류 메시지 유지하기
일반 문자열 규칙의 메시지는 대체로 다음 우선순위로 선택됩니다. size 규칙 등의 특수한 분기는 여기서 다루지 않습니다.
Laravel이
:attribute 등을 치환한 후에 커스텀 replacer가 호출됩니다. replacer의 인수는 선택된 메시지·속성 이름·규칙 이름·파라미터 배열·Validator의 다섯 개입니다. 앞의 예제에서는 사용하지 않는 다섯 번째 인수를 생략했습니다.
애플리케이션 측에서 일본어 기본 문장을 지정하려면 기존 lang/ja/validation.php의 반환 배열에 acme_tracking을 추가합니다. 아래 예는 추가할 항목의 예이며, 다른 내장 메시지를 삭제하는 것이 아닙니다.
__('...')를 실행해 fallback에 저장하면 그 시점 로케일의 문자열이 Factory에 남습니다. 로케일을 전환하는 애플리케이션에서는 오류 생성 시의 번역을 사용합니다.
패키지의 네임스페이스 번역을 사용하는 경우
boot()에서 loadTranslationsFrom(__DIR__.'/../lang', 'acme-courier')를 호출하면 패키지의 lang/ja/validation.php 등을 acme-courier::validation.tracking_code로 참조할 수 있습니다.
단, extend()의 세 번째 인수에 번역 키를 전달하는 것만으로는 자동으로 번역되지 않습니다. Factory는 단순한 fallback 문자열로 보유합니다. 앞의 훅 안의 extend()와 replacer()를 다음 등록으로 바꾸면 fallback이 선택되었을 때만 패키지의 번역을 가져올 수 있습니다.
validation.php에 tracking_code 항목을 준비합니다. 일본어 문장은 앞의 acme_tracking 항목과 같은 것을 사용할 수 있습니다.
여기서는 replacer 안에서 새 번역 문자열을 가져오므로 :attribute도 명시적으로 번역에 전달합니다. Laravel의 표준 치환이 이 반환값에 다시 적용되는 것은 아닙니다. 사용자의 자체 메시지가 선택된 경우에는 그 문장을 유지합니다. 배포 번역의 덮어쓰기는 패키지 번역의 덮어쓰기와 갱신을 참조하세요.
Implicit와 nullable의 실행 조건 이해하기
extendImplicit()은 누락된 속성이나 빈 문자열이어도 검사 대상이 되는 규칙을 등록합니다. “반드시 실패시킨다”거나 “다른 건너뛰기 조건을 모두 무시한다”는 의미가 아닙니다.
표는 각 규칙의 실행 조건 비교입니다.
bail, 속성 제외, stopOnFirstFailure() 등으로 먼저 처리가 멈추는 경우는 별개입니다. HTTP 입력에서는 빈 문자열이 미들웨어에 의해 null로 변환되기도 하므로 빈 문자열과 null을 따로 테스트합니다.
이번 형식 규칙은 일반 extend()와 required의 조합으로 충분합니다. 공개된 규칙을 Implicit로 변경하면 이전에는 검사되지 않던 입력이 실패할 수 있으므로 호환성 변경으로 다룹니다.
Dependent 규칙으로 배열 안의 참조 대상 해석하기
다른 필드의 이름을 파라미터로 받는 규칙에서는extendDependent()를 사용할 수 있습니다. items.*.confirmed_tracking을 검사할 때 파라미터의 items.*.tracking이 같은 행의 items.0.tracking 등으로 변환됩니다. 일반 extend()에서는 이 치환이 이루어지지 않습니다.
다음은 이 구조를 확인하기 위한 일치 규칙입니다. 단순한 일치 검사만이라면 실제 애플리케이션에서는 내장 same을 사용할 수 있습니다. 패키지 고유의 필드 간 검사에 응용할 때의 예로 봐 주세요.
훅에서 받은 Factory에 추가합니다.
getData()는 유효성 검사 대상 데이터이며, 검사가 끝난 데이터만은 아닙니다. 참조하는 쪽과 참조되는 쪽 모두에 필요한 규칙을 정의합니다.
또한 Dependent로 해도 Implicit가 되지는 않습니다. 누락이나 빈 값에 대한 필수 여부는 별도로 지정합니다. 구현은 파라미터에 점의 이스케이프 처리와 * 치환을 수행하므로, 필드 이름 이외의 임의 문자열을 섞는 API는 피합니다.
애플리케이션에서 등록부터 검사까지 테스트하기
패키지와 Service Provider가 로드되는 Laravel 13 애플리케이션에서tests/Feature/TrackingRuleTest.php를 작성합니다. 클래스 단위 테스트에 더해, 실제 Factory를 통해 등록과 메시지 처리를 검사합니다.
장기 유지보수에서 확인할 사항
- 규칙 이름, 파라미터의 의미·개수, 필수 여부, 받아들이는 값의 타입을 공개 API로 기록한다.
- 잘못된 파라미터, 누락·빈 문자열·
null,nullable·sometimes·bail의 조합을 검사한다. - 사용자의
messages(), 속성 표시 이름, 로케일 전환, 패키지 번역의 덮어쓰기를 확인한다. - Dependent 규칙은 여러 행과 중첩된 배열에서 다른 행의 값을 참조하지 않는지 확인한다.
- 미해석·해석된 Factory에 대한 등록을 확인하고, 이미 생성된 Validator에 소급 반영되기를 기대하지 않는다.
- 여러 패키지와의 이름 충돌을 피하고, 같은 이름을 일반·Implicit·Dependent 등록에 섞어 쓰지 않는다.
- 장시간 실행되는 워커에 변경을 반영할 때는 프로세스를 재시작하고, 입력이나 사용자 정보를 공유 핸들러에 남기지 않는다.
참조한 1차 정보
- Laravel 공식 문서: 커스텀 규칙과 Implicit 규칙
- Laravel 공식 문서: 패키지의 Service Provider와 번역
- Factory: make・extend・extendImplicit・extendDependent・replacer
- Validator: 실행 조건・확장 등록・클래스 해석
- ValidationRuleParser: 문자열 규칙 분석
- FormatsMessages: 메시지 우선순위와 치환
- ValidationServiceProvider: validator의 singleton 등록
- ServiceProvider: callAfterResolving
- 공식 테스트: Factory에서 확장과 fallback 전달
- 공식 테스트: 문자열・클래스・Implicit・Dependent 규칙