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
48 changes: 48 additions & 0 deletions docs/validation/using-validation-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,54 @@ 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,
) {
}
}
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a list here with the possible constraints?


The following database constraints are available:

- `WhereConstraint`
- `WhereNotConstraint`
- `WhereNullConstraint`
- `WhereNotNullConstraint`
- `WhereInConstraint`
- `WhereNotInConstraint`

All constraint parameters support `ExternalReference` objects for dynamic values.



## 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
37 changes: 37 additions & 0 deletions src/Attributes/Concerns/AppliesDatabaseConstraints.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Spatie\LaravelData\Attributes\Concerns;

use Closure;
use InvalidArgumentException;
use Spatie\LaravelData\Support\Validation\Constraints\DatabaseConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereInConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereNotConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereNotInConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereNotNullConstraint;
use Spatie\LaravelData\Support\Validation\Constraints\WhereNullConstraint;

trait AppliesDatabaseConstraints
{
/**
* @param object $rule A validation rule that uses the DatabaseRule trait (e.g., Unique, Exists)
*/
protected function applyDatabaseConstraints(object $rule, Closure|DatabaseConstraint|array $constraints): void
{
$constraintsList = is_array($constraints) ? $constraints : [$constraints];

foreach ($constraintsList 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'),
};
}
}
}
17 changes: 17 additions & 0 deletions src/Attributes/Concerns/NormalizesExternalReferences.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Spatie\LaravelData\Attributes\Concerns;

use Spatie\LaravelData\Support\Validation\References\ExternalReference;

trait NormalizesExternalReferences
{
protected function normalizePossibleExternalReferenceParameter(mixed $parameter): mixed
{
if ($parameter instanceof ExternalReference) {
return $parameter->getValue();
}

return $parameter;
}
}
8 changes: 6 additions & 2 deletions src/Attributes/Validation/Exists.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@
use Closure;
use Exception;
use Illuminate\Validation\Rules\Exists as BaseExists;
use Spatie\LaravelData\Attributes\Concerns\AppliesDatabaseConstraints;
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
{
use AppliesDatabaseConstraints;

public function __construct(
protected null|string|ExternalReference $table = null,
protected null|string|ExternalReference $column = 'NULL',
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 +52,7 @@ public function getRule(ValidationPath $path): object|string
}

if ($this->where) {
$rule->where($this->where);
$this->applyDatabaseConstraints($rule, $this->where);
}

return $rule;
Expand Down
13 changes: 3 additions & 10 deletions src/Attributes/Validation/ObjectValidationAttribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,12 @@

namespace Spatie\LaravelData\Attributes\Validation;

use Spatie\LaravelData\Support\Validation\References\ExternalReference;
use Spatie\LaravelData\Attributes\Concerns\NormalizesExternalReferences;
use Spatie\LaravelData\Support\Validation\ValidationPath;

abstract class ObjectValidationAttribute extends ValidationAttribute
{
abstract public function getRule(ValidationPath $path): object|string;

protected function normalizePossibleExternalReferenceParameter(mixed $parameter): mixed
{
if ($parameter instanceof ExternalReference) {
return $parameter->getValue();
}
use NormalizesExternalReferences;

return $parameter;
}
abstract public function getRule(ValidationPath $path): object|string;
}
8 changes: 6 additions & 2 deletions src/Attributes/Validation/Unique.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
use Closure;
use Exception;
use Illuminate\Validation\Rules\Unique as BaseUnique;
use Spatie\LaravelData\Attributes\Concerns\AppliesDatabaseConstraints;
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
{
use AppliesDatabaseConstraints;

public function __construct(
protected null|string|ExternalReference $table = null,
protected null|string|ExternalReference $column = 'NULL',
Expand All @@ -20,7 +24,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 +60,7 @@ public function getRule(ValidationPath $path): object|string
}

if ($this->where) {
$rule->where($this->where);
$this->applyDatabaseConstraints($rule, $this->where);
}

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

namespace Spatie\LaravelData\Support\Validation\Constraints;

use Illuminate\Contracts\Support\Arrayable;
use Spatie\LaravelData\Attributes\Concerns\NormalizesExternalReferences;

abstract class DatabaseConstraint implements Arrayable
{
use NormalizesExternalReferences;
}
23 changes: 23 additions & 0 deletions src/Support/Validation/Constraints/WhereConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Spatie\LaravelData\Support\Validation\Constraints;

use Closure;
use Spatie\LaravelData\Support\Validation\References\ExternalReference;

class WhereConstraint extends DatabaseConstraint
{
public function __construct(
public readonly Closure|string|ExternalReference $column,
public readonly mixed $value = null,
) {
}

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

namespace Spatie\LaravelData\Support\Validation\Constraints;

use BackedEnum;
use Illuminate\Contracts\Support\Arrayable;
use Spatie\LaravelData\Support\Validation\References\ExternalReference;

class WhereInConstraint extends DatabaseConstraint
{
public function __construct(
public readonly string|ExternalReference $column,
public readonly Arrayable|BackedEnum|array|ExternalReference $values,
) {}

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

namespace Spatie\LaravelData\Support\Validation\Constraints;

use Spatie\LaravelData\Support\Validation\References\ExternalReference;

class WhereNotConstraint extends DatabaseConstraint
{
public function __construct(
public readonly string|ExternalReference $column,
public readonly mixed $value,
) {}

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

namespace Spatie\LaravelData\Support\Validation\Constraints;

use BackedEnum;
use Illuminate\Contracts\Support\Arrayable;
use Spatie\LaravelData\Support\Validation\References\ExternalReference;

class WhereNotInConstraint extends DatabaseConstraint
{
public function __construct(
public readonly string|ExternalReference $column,
public readonly Arrayable|BackedEnum|array|ExternalReference $values,
) {}

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

namespace Spatie\LaravelData\Support\Validation\Constraints;

use Spatie\LaravelData\Support\Validation\References\ExternalReference;

class WhereNotNullConstraint extends DatabaseConstraint
{
public function __construct(
public readonly string|ExternalReference $column,
) {}

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

namespace Spatie\LaravelData\Support\Validation\Constraints;

use Spatie\LaravelData\Support\Validation\References\ExternalReference;

class WhereNullConstraint extends DatabaseConstraint
{
public function __construct(
public readonly string|ExternalReference $column,
) {}

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