diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 42ca41d..4975c48 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -7,14 +7,14 @@ jobs:
name: Lint code with PHP-CS-Fixer
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Install dependencies
uses: php-actions/composer@v6
with:
command: install
only_args: -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --ignore-platform-reqs
- php_version: 8.1
+ php_version: 8.3
- name: Run PHP-CS-Fixer
run: vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff --dry-run
@@ -23,14 +23,14 @@ jobs:
name: Lint code with PHP CodeSniffer
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Install dependencies
uses: php-actions/composer@v6
with:
command: install
only_args: -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --ignore-platform-reqs
- php_version: 8.1
+ php_version: 8.3
- name: Run PHP CodeSniffer
run: vendor/bin/phpcs --extensions=php
diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml
index 22f103d..b040f3d 100644
--- a/.github/workflows/unittests.yml
+++ b/.github/workflows/unittests.yml
@@ -11,24 +11,94 @@ jobs:
- php: 8.1
laravel: 10.*
testbench: 8.*
+ phpunit-config: 'phpunit-10.xml'
composer-flag: '--prefer-stable'
- php: 8.2
laravel: 10.*
testbench: 8.*
+ phpunit-config: 'phpunit-10.xml'
+ composer-flag: '--prefer-stable'
+ - php: 8.3
+ laravel: 10.*
+ testbench: 8.*
+ phpunit-config: 'phpunit-10.xml'
composer-flag: '--prefer-stable'
- php: 8.1
laravel: 10.*
testbench: 8.*
+ phpunit-config: 'phpunit-10.xml'
composer-flag: '--prefer-lowest'
- php: 8.2
laravel: 10.*
testbench: 8.*
+ phpunit-config: 'phpunit-10.xml'
+ composer-flag: '--prefer-lowest'
+ - php: 8.3
+ laravel: 10.*
+ testbench: 8.*
+ phpunit-config: 'phpunit-10.xml'
+ composer-flag: '--prefer-lowest'
+ # Laravel 11.*
+ - php: 8.2
+ laravel: 11.*
+ testbench: 9.*
+ phpunit-config: 'phpunit.xml'
+ composer-flag: '--prefer-stable'
+ - php: 8.3
+ laravel: 11.*
+ testbench: 9.*
+ phpunit-config: 'phpunit.xml'
+ composer-flag: '--prefer-stable'
+ - php: 8.2
+ laravel: 11.*
+ testbench: 9.*
+ phpunit-config: 'phpunit.xml'
+ composer-flag: '--prefer-lowest'
+ - php: 8.2
+ laravel: 11.*
+ testbench: 9.*
+ phpunit-config: 'phpunit.xml'
+ composer-flag: '--prefer-lowest'
+ - php: 8.4
+ laravel: 11.*
+ testbench: 9.*
+ phpunit-config: 'phpunit.xml'
+ composer-flag: '--prefer-stable'
+ # Laravel 12.*
+ - php: 8.2
+ laravel: 12.*
+ testbench: 10.*
+ phpunit-config: 'phpunit.xml'
+ composer-flag: '--prefer-stable'
+ - php: 8.3
+ laravel: 12.*
+ testbench: 10.*
+ phpunit-config: 'phpunit.xml'
+ composer-flag: '--prefer-stable'
+ - php: 8.4
+ laravel: 12.*
+ testbench: 10.*
+ phpunit-config: 'phpunit.xml'
+ composer-flag: '--prefer-stable'
+ - php: 8.2
+ laravel: 12.*
+ testbench: 10.*
+ phpunit-config: 'phpunit.xml'
+ composer-flag: '--prefer-lowest'
+ - php: 8.3
+ laravel: 12.*
+ testbench: 10.*
+ phpunit-config: 'phpunit.xml'
+ composer-flag: '--prefer-lowest'
+ - php: 8.4
+ laravel: 12.*
+ testbench: 10.*
+ phpunit-config: 'phpunit.xml'
composer-flag: '--prefer-lowest'
-
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
@@ -44,7 +114,9 @@ jobs:
run: composer update ${{ matrix.composer-flag }} --prefer-dist --no-interaction
- name: Run PHPUnit
- run: XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml
+ run: XDEBUG_MODE=coverage vendor/bin/phpunit --config="${{ matrix.phpunit-config }}" --coverage-text --coverage-clover=coverage.xml
- name: Upload coverage reports to Codecov
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v5
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/composer.json b/composer.json
index cf0eb77..774b9eb 100644
--- a/composer.json
+++ b/composer.json
@@ -1,7 +1,14 @@
{
"name": "korridor/laravel-model-validation-rules",
"description": "A laravel validation rule that uses eloquent to validate if a model exists",
- "keywords": ["validation", "laravel", "rule", "model", "exist", "eloquent"],
+ "keywords": [
+ "validation",
+ "laravel",
+ "rule",
+ "model",
+ "exist",
+ "eloquent"
+ ],
"homepage": "https://github.com/korridor/laravel-model-validation-rules",
"authors": [
{
@@ -12,12 +19,12 @@
"license": "MIT",
"require": {
"php": ">=8.1",
- "illuminate/support": "^10",
- "illuminate/database": "^10"
+ "illuminate/support": "^10|^11|^12.0",
+ "illuminate/database": "^10|^11|^12.0"
},
"require-dev": {
- "orchestra/testbench": "^8.0",
- "phpunit/phpunit": "^10",
+ "orchestra/testbench": "^8.0|^9.0|^10.0",
+ "phpunit/phpunit": "^10|^11.5.3",
"friendsofphp/php-cs-fixer": "^3.6",
"squizlabs/php_codesniffer": "^3.5"
},
@@ -46,5 +53,7 @@
},
"config": {
"sort-packages": true
- }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
}
diff --git a/docker/Dockerfile b/docker/Dockerfile
index a5d71e1..86e2c84 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,4 +1,4 @@
-FROM php:8.1-cli
+FROM php:8.3-cli
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
diff --git a/phpunit-10.xml b/phpunit-10.xml
new file mode 100644
index 0000000..ac59508
--- /dev/null
+++ b/phpunit-10.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ src/
+
+
+
+
+ tests/Feature
+
+
+
+
+
+
diff --git a/phpunit.xml b/phpunit.xml
index ac59508..ab1c97a 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,19 +1,16 @@
-
-
-
- src/
-
-
-
-
- tests/Feature
-
-
-
-
-
+
+
+
+ tests/Feature
+
+
+
+
+
+
+
+ src/
+
+
diff --git a/readme.md b/readme.md
index 291bc54..fdb5a82 100644
--- a/readme.md
+++ b/readme.md
@@ -15,6 +15,9 @@ It uses Eloquent models instead of directly querying the database.
- Soft deletes are working out of the box.
- Logic implemented into the models work in the validation as well. (multi tenancy system, etc.)
+> [!NOTE]
+> Check out **solidtime - The modern Open Source Time-Tracker** at [solidtime.io](https://www.solidtime.io)
+
## Installation
You can install the package via composer with following command:
@@ -33,7 +36,9 @@ composer require korridor/laravel-model-validation-rules "^2.1"
This package is tested for the following Laravel and PHP versions:
- - 10.* (PHP 8.1, 8.2)
+ - 12.* (PHP 8.2, 8.3, 8.4)
+ - 11.* (PHP 8.2, 8.3, 8.4)
+ - 10.* (PHP 8.1, 8.2, 8.3)
## Usage examples
diff --git a/src/Rules/ExistsEloquent.php b/src/Rules/ExistsEloquent.php
index 8ccd714..56efc55 100644
--- a/src/Rules/ExistsEloquent.php
+++ b/src/Rules/ExistsEloquent.php
@@ -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
{
@@ -46,6 +47,18 @@ class ExistsEloquent implements ValidationRule
*/
private ?string $customMessageTranslationKey = null;
+ /**
+ * Include soft deleted models in the query.
+ *
+ * @var bool
+ */
+ private bool $includeSoftDeleted = false;
+
+ /**
+ * @var bool Whether the key field is of type UUID
+ */
+ private bool $isFieldUuid = false;
+
/**
* Create a new rule instance.
*
@@ -60,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 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.
*
@@ -86,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.
*
@@ -97,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();
@@ -110,16 +155,26 @@ public function validate(string $attribute, mixed $value, Closure $fail): void
$builder = $builderClosure($builder);
}
+ if ($this->includeSoftDeleted) {
+ $builder = $builder->withTrashed();
+ }
+
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,
+ ]);
}
}
@@ -141,4 +196,26 @@ public function query(Closure $builderClosure): self
return $this;
}
+
+ /**
+ * Activate or deactivate including soft deleted models in the query.
+ *
+ * @param bool $includeSoftDeleted
+ * @return void
+ */
+ public function setIncludeSoftDeleted(bool $includeSoftDeleted): void
+ {
+ $this->includeSoftDeleted = $includeSoftDeleted;
+ }
+
+ /**
+ * Activate including soft deleted models in the query.
+ * @return $this
+ */
+ public function includeSoftDeleted(): self
+ {
+ $this->setIncludeSoftDeleted(true);
+
+ return $this;
+ }
}
diff --git a/src/Rules/UniqueEloquent.php b/src/Rules/UniqueEloquent.php
index 3769f4a..bad589e 100644
--- a/src/Rules/UniqueEloquent.php
+++ b/src/Rules/UniqueEloquent.php
@@ -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
{
@@ -56,12 +57,24 @@ class UniqueEloquent implements ValidationRule
*/
private ?string $customMessageTranslationKey = null;
+ /**
+ * Include soft deleted models in the query.
+ *
+ * @var bool
+ */
+ private bool $includeSoftDeleted = false;
+
+ /**
+ * @var bool Whether the ID is a UUID
+ */
+ private bool $isFieldUuid = false;
+
/**
* UniqueEloquent constructor.
*
- * @param class-string $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 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)
{
@@ -70,17 +83,32 @@ public function __construct(string $model, ?string $key = null, ?Closure $builde
$this->setBuilderClosure($builderClosure);
}
+ /**
+ * @param class-string $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();
@@ -97,15 +125,20 @@ public function validate(string $attribute, mixed $value, Closure $fail): void
);
}
+ if ($this->includeSoftDeleted) {
+ $builder = $builder->withTrashed();
+ }
+
if ($builder->exists()) {
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,
+ ]);
}
}
}
@@ -113,7 +146,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void
/**
* Set a custom validation message.
*
- * @param string $message
+ * @param string $message
* @return $this
*/
public function withMessage(string $message): self
@@ -126,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
@@ -139,7 +172,7 @@ 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
{
@@ -147,7 +180,7 @@ public function setBuilderClosure(?Closure $builderClosure): void
}
/**
- * @param Closure $builderClosure
+ * @param Closure $builderClosure
* @return $this
*/
public function query(Closure $builderClosure): self
@@ -158,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
{
@@ -169,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
@@ -178,4 +211,41 @@ public function ignore(mixed $id, ?string $column = null): self
return $this;
}
+
+ /**
+ * Activate or deactivate including soft deleted models in the query.
+ *
+ * @param bool $includeSoftDeleted
+ * @return void
+ */
+ 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.
+ *
+ * @return $this
+ */
+ public function includeSoftDeleted(): self
+ {
+ $this->setIncludeSoftDeleted(true);
+
+ return $this;
+ }
}
diff --git a/tests/Feature/ExistsEloquentTest.php b/tests/Feature/ExistsEloquentTest.php
index 6d3447e..685dcab 100644
--- a/tests/Feature/ExistsEloquentTest.php
+++ b/tests/Feature/ExistsEloquentTest.php
@@ -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;
@@ -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]);
}
@@ -255,6 +259,79 @@ public function testValidationPassesIfRuleChecksThatFactExistsAndBelongsToUserUs
$this->assertCount(1, Fact::all());
}
+ /*
+ * Tests for includeSoftDeleted
+ */
+
+ public function testValidationSucceedsIfSoftDeletedEntryExistInDatabaseAndIncludeSoftDeletedFlagIsActive(): void
+ {
+ // Arrange
+ User::create([
+ 'id' => 6,
+ 'other_id' => null,
+ 'name' => 'Testname',
+ 'email' => 'name@test.com',
+ 'password' => bcrypt('secret'),
+ 'remember_token' => Str::random(10),
+ ]);
+ $fact = Fact::create([
+ 'id' => 1,
+ 'user_id' => 6,
+ 'type' => 'type1',
+ 'description' => 'Long desc',
+ ]);
+ $fact->delete();
+
+ $validator = Validator::make([
+ 'id' => 1,
+ ], [
+ 'id' => [(new ExistsEloquent(Fact::class))->includeSoftDeleted()]
+ ]);
+
+ // Act
+ $isValid = $validator->passes();
+ $messages = $validator->messages()->toArray();
+
+ // Assert
+ $this->assertTrue($isValid);
+ $this->assertArrayNotHasKey('id', $messages);
+ $this->assertCount(1, User::withTrashed()->get());
+ $this->assertCount(1, User::all());
+ $this->assertCount(1, Fact::withTrashed()->get());
+ $this->assertCount(0, Fact::all());
+ }
+
+ public function testValidationFailsIfSoftDeletedEntryDoesNotExistInDatabaseAndIncludeSoftDeletedFlagIsActive(): void
+ {
+ // Arrange
+ User::create([
+ 'id' => 6,
+ 'other_id' => null,
+ 'name' => 'Testname',
+ 'email' => 'name@test.com',
+ 'password' => bcrypt('secret'),
+ 'remember_token' => Str::random(10),
+ ]);
+
+ $validator = Validator::make([
+ 'id' => 1,
+ ], [
+ 'id' => [(new ExistsEloquent(Fact::class))->includeSoftDeleted()]
+ ]);
+
+ // Act
+ $isValid = $validator->passes();
+ $messages = $validator->messages()->toArray();
+
+ // Assert
+ $this->assertFalse($isValid);
+ $this->assertEquals('The resource does not exist.', $messages['id'][0]);
+ $this->assertCount(1, User::withTrashed()->get());
+ $this->assertCount(1, User::all());
+ $this->assertCount(0, Fact::withTrashed()->get());
+ $this->assertCount(0, Fact::all());
+ }
+
/*
* Test language support
*/
@@ -332,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]);
+ }
}
diff --git a/tests/Feature/UniqueEloquentTest.php b/tests/Feature/UniqueEloquentTest.php
index 20766a8..b640296 100644
--- a/tests/Feature/UniqueEloquentTest.php
+++ b/tests/Feature/UniqueEloquentTest.php
@@ -257,6 +257,79 @@ public function testValidationFailsIfRuleChecksThatFactExistsAndBelongsToUserUsi
$this->assertCount(1, Fact::all());
}
+ /*
+ * Tests for includeSoftDeleted
+ */
+
+ public function testValidationSucceedsIfSoftDeletedEntryDoesNotExistInDatabaseAndIncludeSoftDeletedFlagIsActive(): void
+ {
+ // Arrange
+ User::create([
+ 'id' => 6,
+ 'other_id' => null,
+ 'name' => 'Testname',
+ 'email' => 'name@test.com',
+ 'password' => bcrypt('secret'),
+ 'remember_token' => Str::random(10),
+ ]);
+
+ $validator = Validator::make([
+ 'id' => 1,
+ ], [
+ 'id' => [(new UniqueEloquent(Fact::class))->includeSoftDeleted()]
+ ]);
+
+ // Act
+ $isValid = $validator->passes();
+ $messages = $validator->messages()->toArray();
+
+ // Assert
+ $this->assertTrue($isValid);
+ $this->assertArrayNotHasKey('id', $messages);
+ $this->assertCount(1, User::withTrashed()->get());
+ $this->assertCount(1, User::all());
+ $this->assertCount(0, Fact::withTrashed()->get());
+ $this->assertCount(0, Fact::all());
+ }
+
+ public function testValidationFailsIfSoftDeletedEntryDoesExistInDatabaseAndIncludeSoftDeletedFlagIsActive(): void
+ {
+ // Arrange
+ User::create([
+ 'id' => 6,
+ 'other_id' => null,
+ 'name' => 'Testname',
+ 'email' => 'name@test.com',
+ 'password' => bcrypt('secret'),
+ 'remember_token' => Str::random(10),
+ ]);
+ $fact = Fact::create([
+ 'id' => 1,
+ 'user_id' => 6,
+ 'type' => 'type1',
+ 'description' => 'Long desc',
+ ]);
+ $fact->delete();
+
+ $validator = Validator::make([
+ 'id' => 1,
+ ], [
+ 'id' => [(new UniqueEloquent(Fact::class))->includeSoftDeleted()]
+ ]);
+
+ // Act
+ $isValid = $validator->passes();
+ $messages = $validator->messages()->toArray();
+
+ // Assert
+ $this->assertFalse($isValid);
+ $this->assertEquals('The resource already exists.', $messages['id'][0]);
+ $this->assertCount(1, User::withTrashed()->get());
+ $this->assertCount(1, User::all());
+ $this->assertCount(1, Fact::withTrashed()->get());
+ $this->assertCount(0, Fact::all());
+ }
+
/*
* Test language support
*/
@@ -427,4 +500,62 @@ public function testIgnoringEntryWithGivenIdColumn(): void
$this->assertTrue($isValid);
$this->assertArrayNotHasKey('id', $messages);
}
+
+ public function testFunctionMakeIsIdenticalToConstructor(): void
+ {
+ // Arrange
+ $message = 'Test';
+ $closure = function (Builder $builder) {
+ return $builder->where('user_id', 6);
+ };
+
+ // Act
+ $rule1 = UniqueEloquent::make(User::class, 'other_id', $closure)->withMessage($message);
+ $rule2 = (new UniqueEloquent(User::class, 'other_id', $closure))->withMessage($message);
+
+ // Assert
+ $this->assertEquals($rule1, $rule2);
+ }
+
+ public function testUuidOptionSkipsValidationIfValueIsNotUuid(): void
+ {
+ // Arrange
+ $validator = Validator::make([
+ 'id' => 'not-a-uuid',
+ ], [
+ 'id' => [(new UniqueEloquent(User::class))->uuid()]
+ ]);
+ $this->db->enableQueryLog();
+
+ // Act
+ $isValid = $validator->passes();
+ $messages = $validator->messages()->toArray();
+
+ // Assert
+ $queryLog = $this->db->getQueryLog();
+ $this->assertCount(0, $queryLog);
+ $this->assertTrue($isValid);
+ $this->assertArrayNotHasKey('id', $messages);
+ }
+
+ public function testUuidOptionSkipsValidationIfValueIsNotString(): void
+ {
+ // Arrange
+ $validator = Validator::make([
+ 'id' => 1,
+ ], [
+ 'id' => [(new UniqueEloquent(User::class))->uuid()]
+ ]);
+ $this->db->enableQueryLog();
+
+ // Act
+ $isValid = $validator->passes();
+ $messages = $validator->messages()->toArray();
+
+ // Assert
+ $queryLog = $this->db->getQueryLog();
+ $this->assertCount(0, $queryLog);
+ $this->assertTrue($isValid);
+ $this->assertArrayNotHasKey('id', $messages);
+ }
}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index a89b984..6cda415 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -6,6 +6,7 @@
use Illuminate\Container\Container;
use Illuminate\Database\Capsule\Manager;
+use Illuminate\Database\Connection;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Events\Dispatcher;
use Illuminate\Foundation\Application;
@@ -14,6 +15,8 @@
abstract class TestCase extends Orchestra
{
+ protected Connection $db;
+
public function setUp(): void
{
parent::setUp();
@@ -57,5 +60,6 @@ protected function setUpDatabase(): void
$table->softDeletes();
$table->timestamps();
});
+ $this->db = $manager->getConnection();
}
}