Skip to content
Open
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
35 changes: 35 additions & 0 deletions docs/validation/using-validation-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,41 @@ The rules will now look like this:
]
```

## Using database constraints

When using `Exists` and `Unique` validation attributes, you can add database constraints to validate against specific conditions:

```php
class UserData extends Data
{
public function __construct(
#[Exists('users', where: new WhereConstraint('active', true))]
public int $user_id,

#[Unique('users', 'email', where: new WhereNullConstraint('deleted_at'))]
public string $email,
) {
}
}
```

You can also combine multiple constraints:

```php
class ProductData extends Data
{
public function __construct(
#[Unique('products', 'sku', where: [
new WhereConstraint('active', true),
new WhereInConstraint('type', ['physical', 'digital']),
new WhereNullConstraint('deleted_at'),
])]
public string $sku,
) {
}
}
```

## Rule attribute

One special attribute is the `Rule` attribute. With it, you can write rules just like you would when creating a custom
Expand Down
25 changes: 23 additions & 2 deletions src/Attributes/Validation/Exists.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@
use Closure;
use Exception;
use Illuminate\Validation\Rules\Exists as BaseExists;
use InvalidArgumentException;
use Spatie\LaravelData\Support\Validation\Constraints\WhereConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereNotConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereNullConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereNotNullConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereInConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereNotInConstraint;
use Spatie\LaravelData\Support\Validation\References\ExternalReference;
use Spatie\LaravelData\Support\Validation\ValidationPath;
use Spatie\LaravelData\Support\Validation\Constraints\DatabaseConstraint;

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)]
class Exists extends ObjectValidationAttribute
Expand All @@ -18,7 +26,7 @@ public function __construct(
protected null|string|ExternalReference $connection = null,
protected bool|ExternalReference $withoutTrashed = false,
protected string|ExternalReference $deletedAtColumn = 'deleted_at',
protected ?Closure $where = null,
protected null|Closure|DatabaseConstraint|array $where = null,
protected ?BaseExists $rule = null,
) {
if ($rule === null && $table === null) {
Expand Down Expand Up @@ -48,7 +56,20 @@ public function getRule(ValidationPath $path): object|string
}

if ($this->where) {
$rule->where($this->where);
$constraints = is_array($this->where) ? $this->where : [$this->where];

foreach ($constraints as $constraint) {
match (true) {
$constraint instanceof Closure => $rule->where($constraint),
$constraint instanceof WhereConstraint => $rule->where(...$constraint->toArray()),
$constraint instanceof WhereNotConstraint => $rule->whereNot(...$constraint->toArray()),
$constraint instanceof WhereNullConstraint => $rule->whereNull(...$constraint->toArray()),
$constraint instanceof WhereNotNullConstraint => $rule->whereNotNull(...$constraint->toArray()),
$constraint instanceof WhereInConstraint => $rule->whereIn(...$constraint->toArray()),
$constraint instanceof WhereNotInConstraint => $rule->whereNotIn(...$constraint->toArray()),
default => throw new InvalidArgumentException('Each where item must be a DatabaseConstraint or Closure'),
};
}
}

return $rule;
Expand Down
25 changes: 23 additions & 2 deletions src/Attributes/Validation/Unique.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@
use Closure;
use Exception;
use Illuminate\Validation\Rules\Unique as BaseUnique;
use InvalidArgumentException;
use Spatie\LaravelData\Support\Validation\Constraints\WhereConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereNotConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereNullConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereNotNullConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereInConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereNotInConstraint;
use Spatie\LaravelData\Support\Validation\References\ExternalReference;
use Spatie\LaravelData\Support\Validation\ValidationPath;
use Spatie\LaravelData\Support\Validation\Constraints\DatabaseConstraint;

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)]
class Unique extends ObjectValidationAttribute
Expand All @@ -20,7 +28,7 @@ public function __construct(
protected null|string|ExternalReference $ignoreColumn = null,
protected bool|ExternalReference $withoutTrashed = false,
protected string|ExternalReference $deletedAtColumn = 'deleted_at',
protected ?Closure $where = null,
protected null|Closure|DatabaseConstraint|array $where = null,
protected ?BaseUnique $rule = null
) {
if ($table === null && $rule === null) {
Expand Down Expand Up @@ -56,7 +64,20 @@ public function getRule(ValidationPath $path): object|string
}

if ($this->where) {
$rule->where($this->where);
$constraints = is_array($this->where) ? $this->where : [$this->where];

foreach ($constraints as $constraint) {
match (true) {
$constraint instanceof Closure => $rule->where($constraint),
$constraint instanceof WhereConstraint => $rule->where(...$constraint->toArray()),
$constraint instanceof WhereNotConstraint => $rule->whereNot(...$constraint->toArray()),
$constraint instanceof WhereNullConstraint => $rule->whereNull(...$constraint->toArray()),
$constraint instanceof WhereNotNullConstraint => $rule->whereNotNull(...$constraint->toArray()),
$constraint instanceof WhereInConstraint => $rule->whereIn(...$constraint->toArray()),
$constraint instanceof WhereNotInConstraint => $rule->whereNotIn(...$constraint->toArray()),
default => throw new InvalidArgumentException('Each where item must be a DatabaseConstraint or Closure'),
};
}
}

return $rule;
Expand Down
9 changes: 9 additions & 0 deletions src/Support/Validation/Constraints/DatabaseConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Spatie\LaravelData\Support\Validation\Constraints;

use Illuminate\Contracts\Support\Arrayable;

interface DatabaseConstraint extends Arrayable
{
}
17 changes: 17 additions & 0 deletions src/Support/Validation/Constraints/WhereConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Spatie\LaravelData\Support\Validation\Constraints;

class WhereConstraint implements DatabaseConstraint
{
public function __construct(
public readonly mixed $column,
public readonly mixed $value = null,
) {
}

public function toArray(): array
{
return [$this->column, $this->value];
}
}
16 changes: 16 additions & 0 deletions src/Support/Validation/Constraints/WhereInConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Spatie\LaravelData\Support\Validation\Constraints;

class WhereInConstraint implements DatabaseConstraint
{
public function __construct(
public readonly mixed $column,
public readonly mixed $values,
) {}

public function toArray(): array
{
return [$this->column, $this->values];
}
}
16 changes: 16 additions & 0 deletions src/Support/Validation/Constraints/WhereNotConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Spatie\LaravelData\Support\Validation\Constraints;

class WhereNotConstraint implements DatabaseConstraint
{
public function __construct(
public readonly mixed $column,
public readonly mixed $value,
) {}

public function toArray(): array
{
return [$this->column, $this->value];
}
}
16 changes: 16 additions & 0 deletions src/Support/Validation/Constraints/WhereNotInConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Spatie\LaravelData\Support\Validation\Constraints;

class WhereNotInConstraint implements DatabaseConstraint
{
public function __construct(
public readonly mixed $column,
public readonly mixed $values,
) {}

public function toArray(): array
{
return [$this->column, $this->values];
}
}
15 changes: 15 additions & 0 deletions src/Support/Validation/Constraints/WhereNotNullConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Spatie\LaravelData\Support\Validation\Constraints;

class WhereNotNullConstraint implements DatabaseConstraint
{
public function __construct(
public readonly mixed $column,
) {}

public function toArray(): array
{
return [$this->column];
}
}
15 changes: 15 additions & 0 deletions src/Support/Validation/Constraints/WhereNullConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Spatie\LaravelData\Support\Validation\Constraints;

class WhereNullConstraint implements DatabaseConstraint
{
public function __construct(
public readonly mixed $column,
) {}

public function toArray(): array
{
return [$this->column];
}
}
Loading
Loading