diff --git a/src/Phaseolies/Console/Commands/MakeRuleCommand.php b/src/Phaseolies/Console/Commands/MakeRuleCommand.php new file mode 100644 index 00000000..ccce222d --- /dev/null +++ b/src/Phaseolies/Console/Commands/MakeRuleCommand.php @@ -0,0 +1,104 @@ +executeWithTiming(function () { + $name = $this->argument('name'); + $parts = explode('/', $name); + $className = array_pop($parts); + $namespace = 'App\\Http\\Validations\\Rules' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); + $filePath = base_path('app/Http/Validations/Rules/' . str_replace('/', DIRECTORY_SEPARATOR, $name) . '.php'); + + // Check if rule already exists + if (file_exists($filePath)) { + $this->displayError('Rule already exists at:'); + $this->line('' . str_replace(base_path(), '', $filePath) . ''); + return Command::FAILURE; + } + + // Create directory if needed + $directoryPath = dirname($filePath); + if (!is_dir($directoryPath)) { + mkdir($directoryPath, 0755, true); + } + + // Generate and save rule class + $content = $this->generateRuleContent($namespace, $className); + file_put_contents($filePath, $content); + + $this->displaySuccess('Rule created successfully'); + $this->line('🛡️ File: ' . str_replace(base_path('/'), '', $filePath) . ''); + $this->newLine(); + $this->line('🔒 Class: ' . $className . ''); + + return Command::SUCCESS; + }); + } + + /** + * Generate rule class content. + */ + protected function generateRuleContent(string $namespace, string $className): string + { + return << + */ + private array $conditions = []; + + /** + * @param RuleInterface $rule + */ + private function __construct(RuleInterface $rule) + { + $this->rule = $rule; + } + + /** + * Bind a custom rule class to the validation pipeline. + * + * @param RuleInterface $rule + * @return static + */ + public static function to(RuleInterface $rule): static + { + return new static($rule); + } + + /** + * Set conditions that must ALL be present in the request input for this rule to be evaluated + * + * @param array $conditions + * @return static + */ + public function context(array $conditions): static + { + $this->conditions = $conditions; + + return $this; + } + + /** + * Evaluate the bound rule against the full request input + * + * @param string $field + * @param array $input + * @return string|null + */ + public function evaluate(string $field, array $input): ?string + { + // Skip rule when any context condition is not satisfied + foreach ($this->conditions as $contextField => $expectedValue) { + if (($input[$contextField] ?? null) !== $expectedValue) { + return null; + } + } + + $value = $input[$field] ?? null; + + if (!$this->rule->passes($field, $value, $input)) { + return $this->rule->message($field); + } + + return null; + } +} diff --git a/src/Phaseolies/Http/Validation/Contracts/RuleInterface.php b/src/Phaseolies/Http/Validation/Contracts/RuleInterface.php new file mode 100644 index 00000000..9c177051 --- /dev/null +++ b/src/Phaseolies/Http/Validation/Contracts/RuleInterface.php @@ -0,0 +1,24 @@ + $value) { + // Custom rule class bound via Bind::to(new Rule())->context([...]) + if ($value instanceof Bind) { + $errorMessage = $value->evaluate($fieldName, $input); + if ($errorMessage) { + $errors[$fieldName][] = $errorMessage; + } + continue; + } + $fieldRules = explode("|", $value); foreach ($fieldRules as $rule) { $ruleValue = $this->_getRuleSuffix($rule); diff --git a/src/Phaseolies/Support/Validation/Sanitizer.php b/src/Phaseolies/Support/Validation/Sanitizer.php index 0c534cfb..8b1e3f61 100644 --- a/src/Phaseolies/Support/Validation/Sanitizer.php +++ b/src/Phaseolies/Support/Validation/Sanitizer.php @@ -3,6 +3,7 @@ namespace Phaseolies\Support\Validation; use Phaseolies\Http\Support\ValidationRules; +use Phaseolies\Http\Validation\Bind; class Sanitizer { @@ -68,6 +69,15 @@ public function request(array $data, array $rules): self public function validate(): bool { foreach ($this->rules as $field => $ruleString) { + // Custom rule class bound via Bind::to(new Rule())->context([...]) + if ($ruleString instanceof Bind) { + $errorMessage = $ruleString->evaluate($field, $this->data); + if ($errorMessage) { + $this->addError($field, $errorMessage); + } + continue; + } + $rulesArray = explode('|', $ruleString); foreach ($rulesArray as $rule) {