1616use Attributes \Validation \Validators \TypeHintValidator ;
1717use ReflectionClass ;
1818use ReflectionException ;
19+ use ReflectionFunction ;
20+ use ReflectionParameter ;
1921use ReflectionProperty ;
2022use Respect \Validation \Exceptions \ValidationException as RespectValidationException ;
2123use Respect \Validation \Factory ;
@@ -86,7 +88,11 @@ public function validate(array $data, string|object $model): object
8688
8789 if (! array_key_exists ($ aliasName , $ data )) {
8890 if (! $ reflectionProperty ->isInitialized ($ validModel )) {
89- $ errorInfo ->addError ("Missing required property ' $ aliasName' " );
91+ try {
92+ $ errorInfo ->addError ("Missing required property ' $ aliasName' " );
93+ } catch (StopValidationException $ e ) {
94+ break ;
95+ }
9096 }
9197
9298 $ this ->context ->pop ('internal.currentProperty ' );
@@ -118,6 +124,72 @@ public function validate(array $data, string|object $model): object
118124 return $ validModel ;
119125 }
120126
127+ /**
128+ * Validates a given data according to a given model
129+ *
130+ * @param array $data - Data to validate
131+ * @param callable $call - Callable to validate data against
132+ * @return array - Returns an array with the necessary arguments for the callable
133+ *
134+ * @throws ValidationException - If validation fails
135+ * @throws ContextPropertyException - If unable to retrieve a given context property
136+ * @throws ReflectionException
137+ * @throws InvalidOptionException
138+ */
139+ public function validateCallable (array $ data , callable $ call ): array
140+ {
141+ $ arguments = [];
142+ $ reflectionFunction = new ReflectionFunction ($ call );
143+ $ errorInfo = $ this ->context ->getOptional (ErrorHolder::class) ?: new ErrorHolder ($ this ->context );
144+ $ this ->context ->set (ErrorHolder::class, $ errorInfo , override: true );
145+ $ defaultAliasGenerator = $ this ->getDefaultAliasGenerator ($ reflectionFunction );
146+ foreach ($ reflectionFunction ->getParameters () as $ parameter ) {
147+ if (! $ this ->isToValidate ($ parameter )) {
148+ continue ;
149+ }
150+
151+ $ propertyName = $ parameter ->getName ();
152+ $ aliasName = $ this ->getAliasName ($ parameter , $ defaultAliasGenerator );
153+ $ this ->context ->push ('internal.currentProperty ' , $ propertyName );
154+
155+ if (! array_key_exists ($ aliasName , $ data )) {
156+ if (! $ parameter ->isDefaultValueAvailable ()) {
157+ try {
158+ $ errorInfo ->addError ("Missing required argument ' $ aliasName' " );
159+ } catch (StopValidationException $ error ) {
160+ break ;
161+ }
162+ }
163+
164+ $ this ->context ->pop ('internal.currentProperty ' );
165+
166+ continue ;
167+ }
168+
169+ $ propertyValue = $ data [$ aliasName ];
170+ $ property = new Property ($ parameter , $ propertyValue );
171+ $ this ->context ->set (Property::class, $ property , override: true );
172+
173+ try {
174+ $ this ->validator ->validate ($ property , $ this ->context );
175+ $ arguments [$ parameter ->getName ()] = $ property ->getValue ();
176+ } catch (ValidationException |RespectValidationException $ error ) {
177+ $ errorInfo ->addError ($ error );
178+ } catch (ContinueValidationException $ error ) {
179+ } catch (StopValidationException $ error ) {
180+ break ;
181+ } finally {
182+ $ this ->context ->pop ('internal.currentProperty ' );
183+ }
184+ }
185+
186+ if ($ errorInfo ->hasErrors ()) {
187+ throw new ValidationException ('Invalid data ' , $ errorInfo );
188+ }
189+
190+ return $ arguments ;
191+ }
192+
121193 protected function getDefaultPropertyValidator (): PropertyValidator
122194 {
123195 $ chainRulesExtractor = new ChainValidator ;
@@ -133,9 +205,9 @@ protected function getDefaultPropertyValidator(): PropertyValidator
133205 * @throws ContextPropertyException
134206 * @throws InvalidOptionException
135207 */
136- protected function getDefaultAliasGenerator (ReflectionClass $ reflectionClass ): callable
208+ protected function getDefaultAliasGenerator (ReflectionClass | ReflectionFunction $ reflection ): callable
137209 {
138- $ allAttributes = $ reflectionClass ->getAttributes (Options \AliasGenerator::class);
210+ $ allAttributes = $ reflection ->getAttributes (Options \AliasGenerator::class);
139211 foreach ($ allAttributes as $ attribute ) {
140212 $ instance = $ attribute ->newInstance ();
141213
@@ -155,10 +227,10 @@ protected function getDefaultAliasGenerator(ReflectionClass $reflectionClass): c
155227 /**
156228 * Retrieves the alias for a given property
157229 */
158- protected function getAliasName (ReflectionProperty $ reflectionProperty , callable $ defaultAliasGenerator ): string
230+ protected function getAliasName (ReflectionProperty | ReflectionParameter $ reflection , callable $ defaultAliasGenerator ): string
159231 {
160- $ propertyName = $ reflectionProperty ->getName ();
161- $ allAttributes = $ reflectionProperty ->getAttributes (Options \Alias::class);
232+ $ propertyName = $ reflection ->getName ();
233+ $ allAttributes = $ reflection ->getAttributes (Options \Alias::class);
162234 foreach ($ allAttributes as $ attribute ) {
163235 $ instance = $ attribute ->newInstance ();
164236
@@ -171,9 +243,9 @@ protected function getAliasName(ReflectionProperty $reflectionProperty, callable
171243 /**
172244 * Checks if a given property is to be ignored
173245 */
174- protected function isToValidate (ReflectionProperty $ reflectionProperty ): bool
246+ protected function isToValidate (ReflectionProperty | ReflectionParameter $ reflection ): bool
175247 {
176- $ allAttributes = $ reflectionProperty ->getAttributes (Options \Ignore::class);
248+ $ allAttributes = $ reflection ->getAttributes (Options \Ignore::class);
177249 foreach ($ allAttributes as $ attribute ) {
178250 $ instance = $ attribute ->newInstance ();
179251
0 commit comments