Skip to content

Commit

Permalink
Add make and uuid functions
Browse files Browse the repository at this point in the history
  • Loading branch information
korridor committed Oct 14, 2024
1 parent 6cecadd commit e7c27b9
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 27 deletions.
62 changes: 53 additions & 9 deletions src/Rules/ExistsEloquent.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

class ExistsEloquent implements ValidationRule
{
Expand Down Expand Up @@ -53,6 +54,11 @@ class ExistsEloquent implements ValidationRule
*/
private bool $includeSoftDeleted = false;

/**
* @var bool Whether the key field is of type UUID
*/
private bool $isFieldUuid = false;

/**
* Create a new rule instance.
*
Expand All @@ -67,6 +73,18 @@ public function __construct(string $model, ?string $key = null, ?Closure $builde
$this->setBuilderClosure($builderClosure);
}

/**
* Create a new rule instance.
*
* @param class-string<Model> $model Class name of model
* @param string|null $key Relevant key in the model
* @param Closure|null $builderClosure Closure that can extend the eloquent builder
*/
public static function make(string $model, ?string $key = null, ?Closure $builderClosure = null): self
{
return new self($model, $key, $builderClosure);
}

/**
* Set a custom validation message.
*
Expand All @@ -93,6 +111,20 @@ public function withCustomTranslation(string $translationKey): self
return $this;
}

/**
* The field has the data type UUID.
* If the field is not a UUID, the validation will fail, before the query is executed.
* This is useful for example for Postgres databases where queries fail if a field with UUID data type is queried with a non-UUID value.
*
* @return $this
*/
public function uuid(): self
{
$this->isFieldUuid = true;

return $this;
}

/**
* Determine if the validation rule passes.
*
Expand All @@ -104,6 +136,12 @@ public function withCustomTranslation(string $translationKey): self
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if ($this->isFieldUuid) {
if (!is_string($value) || !Str::isUuid($value)) {
$this->fail($attribute, $value, $fail);
return;
}
}
/** @var Model|Builder $builder */
$builder = new $this->model();
$modelKeyName = $builder->getKeyName();
Expand All @@ -122,15 +160,21 @@ public function validate(string $attribute, mixed $value, Closure $fail): void
}

if ($builder->doesntExist()) {
if ($this->customMessage !== null) {
$fail($this->customMessage);
} else {
$fail($this->customMessageTranslationKey ?? 'modelValidationRules::validation.exists_model')->translate([
'attribute' => $attribute,
'model' => strtolower(class_basename($this->model)),
'value' => $value,
]);
}
$this->fail($attribute, $value, $fail);
return;
}
}

private function fail(string $attribute, mixed $value, Closure $fail): void
{
if ($this->customMessage !== null) {
$fail($this->customMessage);
} else {
$fail($this->customMessageTranslationKey ?? 'modelValidationRules::validation.exists_model')->translate([
'attribute' => $attribute,
'model' => strtolower(class_basename($this->model)),
'value' => $value,
]);
}
}

Expand Down
72 changes: 54 additions & 18 deletions src/Rules/UniqueEloquent.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

class UniqueEloquent implements ValidationRule
{
Expand Down Expand Up @@ -63,12 +64,17 @@ class UniqueEloquent implements ValidationRule
*/
private bool $includeSoftDeleted = false;

/**
* @var bool Whether the ID is a UUID
*/
private bool $isFieldUuid = false;

/**
* UniqueEloquent constructor.
*
* @param class-string<Model> $model Class name of model.
* @param string|null $key Relevant key in the model.
* @param Closure|null $builderClosure Closure that can extend the eloquent builder
* @param class-string<Model> $model Class name of model.
* @param string|null $key Relevant key in the model.
* @param Closure|null $builderClosure Closure that can extend the eloquent builder
*/
public function __construct(string $model, ?string $key = null, ?Closure $builderClosure = null)
{
Expand All @@ -77,17 +83,32 @@ public function __construct(string $model, ?string $key = null, ?Closure $builde
$this->setBuilderClosure($builderClosure);
}

/**
* @param class-string<Model> $model Class name of model.
* @param string|null $key Relevant key in the model.
* @param Closure|null $builderClosure Closure that can extend the eloquent builder
*/
public static function make(string $model, ?string $key = null, ?Closure $builderClosure = null): self
{
return new self($model, $key, $builderClosure);
}

/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @param Closure $fail
* @param string $attribute
* @param mixed $value
* @param Closure $fail
*
* @return void
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if ($this->isFieldUuid) {
if (!is_string($value) || !Str::isUuid($value)) {
return;
}
}
/** @var Model|Builder $builder */
$builder = new $this->model();
$modelKeyName = $builder->getKeyName();
Expand All @@ -112,19 +133,20 @@ public function validate(string $attribute, mixed $value, Closure $fail): void
if ($this->customMessage !== null) {
$fail($this->customMessage);
} else {
$fail($this->customMessageTranslationKey ?? 'modelValidationRules::validation.unique_model')->translate([
'attribute' => $attribute,
'model' => strtolower(class_basename($this->model)),
'value' => $value,
]);
$fail($this->customMessageTranslationKey ?? 'modelValidationRules::validation.unique_model')
->translate([
'attribute' => $attribute,
'model' => strtolower(class_basename($this->model)),
'value' => $value,
]);
}
}
}

/**
* Set a custom validation message.
*
* @param string $message
* @param string $message
* @return $this
*/
public function withMessage(string $message): self
Expand All @@ -137,7 +159,7 @@ public function withMessage(string $message): self
/**
* Set a translated custom validation message.
*
* @param string $translationKey
* @param string $translationKey
* @return $this
*/
public function withCustomTranslation(string $translationKey): self
Expand All @@ -150,15 +172,15 @@ public function withCustomTranslation(string $translationKey): self
/**
* Set a closure that can extend the eloquent builder.
*
* @param Closure|null $builderClosure
* @param Closure|null $builderClosure
*/
public function setBuilderClosure(?Closure $builderClosure): void
{
$this->builderClosure = $builderClosure;
}

/**
* @param Closure $builderClosure
* @param Closure $builderClosure
* @return $this
*/
public function query(Closure $builderClosure): self
Expand All @@ -169,8 +191,8 @@ public function query(Closure $builderClosure): self
}

/**
* @param mixed $id
* @param string|null $column
* @param mixed $id
* @param string|null $column
*/
public function setIgnore(mixed $id, ?string $column = null): void
{
Expand All @@ -180,7 +202,7 @@ public function setIgnore(mixed $id, ?string $column = null): void

/**
* @param mixed $id
* @param string|null $column
* @param string|null $column
* @return UniqueEloquent
*/
public function ignore(mixed $id, ?string $column = null): self
Expand All @@ -201,6 +223,20 @@ public function setIncludeSoftDeleted(bool $includeSoftDeleted): void
$this->includeSoftDeleted = $includeSoftDeleted;
}

/**
* The field has the data type UUID.
* If a value is not a UUID, the validation will be skipped.
* This is useful for example for Postgres databases where queries fail if a field with UUID data type is queried with a non-UUID value.
*
* @return $this
*/
public function uuid(): self
{
$this->isFieldUuid = true;

return $this;
}

/**
* Activate including soft deleted models in the query.
*
Expand Down
62 changes: 62 additions & 0 deletions tests/Feature/ExistsEloquentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
Expand All @@ -30,12 +31,15 @@ public function testValidationFailsIfEntryDoesNotExistInDatabase(): void
], [
'id' => [new ExistsEloquent(User::class)]
]);
$this->db->enableQueryLog();

// Act
$isValid = $validator->passes();
$messages = $validator->messages()->toArray();

// Assert
$queryLog = $this->db->getQueryLog();
$this->assertCount(1, $queryLog);
$this->assertFalse($isValid);
$this->assertEquals('The resource does not exist.', $messages['id'][0]);
}
Expand Down Expand Up @@ -405,4 +409,62 @@ public function testValidationMessageIsLaravelTranslationIfCustomTranslationIsSe
$this->assertFalse($isValid);
$this->assertEquals('A user with the id "1" does not exist. / Test', $messages['id'][0]);
}

public function testFunctionMakeIsIdenticalToConstructor(): void
{
// Arrange
$message = 'Test';
$closure = function (Builder $builder) {
return $builder->where('user_id', 6);
};

// Act
$rule1 = ExistsEloquent::make(User::class, 'other_id', $closure)->withMessage($message);
$rule2 = (new ExistsEloquent(User::class, 'other_id', $closure))->withMessage($message);

// Assert
$this->assertEquals($rule1, $rule2);
}

public function testUuidOptionMakesRuleFailIfValueIsNotUuidBeforeQueryingTheDatabase(): void
{
// Arrange
$validator = Validator::make([
'id' => 'not-a-uuid',
], [
'id' => [(new ExistsEloquent(User::class))->uuid()]
]);
$this->db->enableQueryLog();

// Act
$isValid = $validator->passes();
$messages = $validator->messages()->toArray();

// Assert
$queryLog = $this->db->getQueryLog();
$this->assertCount(0, $queryLog);
$this->assertFalse($isValid);
$this->assertEquals('The resource does not exist.', $messages['id'][0]);
}

public function testUuidOptionMakesRuleFailIfValueIsNotStringBeforeQueryingTheDatabase(): void
{
// Arrange
$validator = Validator::make([
'id' => 1,
], [
'id' => [(new ExistsEloquent(User::class))->uuid()]
]);
$this->db->enableQueryLog();

// Act
$isValid = $validator->passes();
$messages = $validator->messages()->toArray();

// Assert
$queryLog = $this->db->getQueryLog();
$this->assertCount(0, $queryLog);
$this->assertFalse($isValid);
$this->assertEquals('The resource does not exist.', $messages['id'][0]);
}
}
Loading

0 comments on commit e7c27b9

Please sign in to comment.