Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions src/Phaseolies/Console/Commands/MakeRuleCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

namespace Phaseolies\Console\Commands;

use Phaseolies\Console\Schedule\Command;

class MakeRuleCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $name = 'make:rule {name : The name of the rule class}';

/**
* The description of the console command.
*
* @var string
*/
protected $description = 'Create a new validation rule class';

/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
return $this->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('<fg=white>' . 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('<fg=yellow>🛡️ File:</> <fg=white>' . str_replace(base_path('/'), '', $filePath) . '</>');
$this->newLine();
$this->line('<fg=yellow>🔒 Class:</> <fg=white>' . $className . '</>');

return Command::SUCCESS;
});
}

/**
* Generate rule class content.
*/
protected function generateRuleContent(string $namespace, string $className): string
{
return <<<EOT
<?php

namespace {$namespace};

use Phaseolies\Http\Validation\Contracts\RuleInterface;

class {$className} implements RuleInterface
{
/**
* Determine if the given field value passes the domain validation rule.
*
* @param string \$field The name of the field being validated.
* @param mixed \$value The value of the field to validate.
* @param array \$input The full input data array.
* @return bool
*/
public function passes(string \$field, mixed \$value, array \$input): bool
{
//
}

/**
* Get the validation error message for the rule.
*
* @param string \$field The name of the field being validated.
* @return string
*/
public function message(string \$field): string
{
//
}
}

EOT;
}
}
79 changes: 79 additions & 0 deletions src/Phaseolies/Http/Validation/Bind.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace Phaseolies\Http\Validation;

use Phaseolies\Http\Validation\Contracts\RuleInterface;

class Bind
{
/**
* The custom rule instance to be evaluated.
*
* @var RuleInterface
*/
private RuleInterface $rule;

/**
* Conditions that must ALL be satisfied for the rule to run
*
* @var array<string, mixed>
*/
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<string, mixed> $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;
}
}
24 changes: 24 additions & 0 deletions src/Phaseolies/Http/Validation/Contracts/RuleInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Phaseolies\Http\Validation\Contracts;

interface RuleInterface
{
/**
* Determine if the validation rule passes.
*
* @param string $field
* @param mixed $value
* @param array $input
* @return bool
*/
public function passes(string $field, mixed $value, array $input): bool;

/**
* Return the validation error message when the rule fails.
*
* @param string $field
* @return string
*/
public function message(string $field): string;
}
10 changes: 10 additions & 0 deletions src/Phaseolies/Http/Validation/Rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Phaseolies\Http\Support\ValidationRules;
use Phaseolies\Http\Response;
use Phaseolies\Http\Exceptions\HttpResponseException;
use Phaseolies\Http\Validation\Bind;

trait Rule
{
Expand All @@ -26,6 +27,15 @@ public function sanitize(array $rules): array|Response

if (is_array($input)) {
foreach ($rules as $fieldName => $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);
Expand Down
10 changes: 10 additions & 0 deletions src/Phaseolies/Support/Validation/Sanitizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Phaseolies\Support\Validation;

use Phaseolies\Http\Support\ValidationRules;
use Phaseolies\Http\Validation\Bind;

class Sanitizer
{
Expand Down Expand Up @@ -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) {
Expand Down
Loading