Skip to content

Commit c8097ef

Browse files
[10.x] "Can" validation rule (#47371)
* Add "can" validation rule * Add tests * Fix grammar in validation message * Fix doc type * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 783a000 commit c8097ef

File tree

4 files changed

+181
-0
lines changed

4 files changed

+181
-0
lines changed

src/Illuminate/Translation/lang/en/validation.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
'string' => 'The :attribute field must be between :min and :max characters.',
3333
],
3434
'boolean' => 'The :attribute field must be true or false.',
35+
'can' => 'The :attribute field contains an unauthorized value.',
3536
'confirmed' => 'The :attribute field confirmation does not match.',
3637
'current_password' => 'The password is incorrect.',
3738
'date' => 'The :attribute field must be a valid date.',

src/Illuminate/Validation/Rule.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Contracts\Support\Arrayable;
66
use Illuminate\Support\Traits\Macroable;
7+
use Illuminate\Validation\Rules\Can;
78
use Illuminate\Validation\Rules\Dimensions;
89
use Illuminate\Validation\Rules\Enum;
910
use Illuminate\Validation\Rules\ExcludeIf;
@@ -20,6 +21,18 @@ class Rule
2021
{
2122
use Macroable;
2223

24+
/**
25+
* Get a can constraint builder instance.
26+
*
27+
* @param string $ability
28+
* @param mixed ...$arguments
29+
* @return \Illuminate\Validation\Rules\Can
30+
*/
31+
public static function can($ability, ...$arguments)
32+
{
33+
return new Can($ability, $arguments);
34+
}
35+
2336
/**
2437
* Create a new conditional rule set.
2538
*
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace Illuminate\Validation\Rules;
4+
5+
use Illuminate\Contracts\Validation\Rule;
6+
use Illuminate\Support\Facades\Gate;
7+
8+
class Can implements Rule
9+
{
10+
/**
11+
* The ability to check.
12+
*
13+
* @var string
14+
*/
15+
protected $ability;
16+
17+
/**
18+
* The arguments to pass to the authorization check.
19+
*
20+
* @var array
21+
*/
22+
protected $arguments;
23+
24+
/**
25+
* Constructor.
26+
*
27+
* @param string $ability
28+
* @param array $arguments
29+
*/
30+
public function __construct($ability, array $arguments = [])
31+
{
32+
$this->ability = $ability;
33+
$this->arguments = $arguments;
34+
}
35+
36+
/**
37+
* Determine if the validation rule passes.
38+
*
39+
* @param string $attribute
40+
* @param mixed $value
41+
* @return bool
42+
*/
43+
public function passes($attribute, $value)
44+
{
45+
$arguments = $this->arguments;
46+
47+
$model = array_shift($arguments);
48+
49+
return Gate::allows($this->ability, array_filter([$model, ...$arguments, $value]));
50+
}
51+
52+
/**
53+
* Get the validation error message.
54+
*
55+
* @return array
56+
*/
57+
public function message()
58+
{
59+
$message = trans('validation.can');
60+
61+
return $message === 'validation.can'
62+
? ['The :attribute field contains an unauthorized value.']
63+
: $message;
64+
}
65+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Validation;
4+
5+
use Illuminate\Auth\Access\Gate;
6+
use Illuminate\Container\Container;
7+
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
8+
use Illuminate\Support\Facades\Facade;
9+
use Illuminate\Translation\ArrayLoader;
10+
use Illuminate\Translation\Translator;
11+
use Illuminate\Validation\Rules\Can;
12+
use Illuminate\Validation\ValidationServiceProvider;
13+
use Illuminate\Validation\Validator;
14+
use PHPUnit\Framework\TestCase;
15+
use stdClass;
16+
17+
class ValidationRuleCanTest extends TestCase
18+
{
19+
protected $container;
20+
protected $user;
21+
protected $router;
22+
23+
protected function setUp(): void
24+
{
25+
parent::setUp();
26+
27+
$this->user = new stdClass;
28+
29+
Container::setInstance($this->container = new Container);
30+
31+
$this->container->singleton(GateContract::class, function () {
32+
return new Gate($this->container, function () {
33+
return $this->user;
34+
});
35+
});
36+
37+
$this->container->bind('translator', function () {
38+
return new Translator(
39+
new ArrayLoader, 'en'
40+
);
41+
});
42+
43+
Facade::setFacadeApplication($this->container);
44+
45+
(new ValidationServiceProvider($this->container))->register();
46+
}
47+
48+
protected function tearDown(): void
49+
{
50+
Container::setInstance(null);
51+
52+
Facade::clearResolvedInstances();
53+
54+
Facade::setFacadeApplication(null);
55+
}
56+
57+
public function testValidationFails()
58+
{
59+
$this->gate()->define('update-company', function ($user, $value) {
60+
$this->assertEquals('1', $value);
61+
62+
return false;
63+
});
64+
65+
$v = new Validator(
66+
resolve('translator'),
67+
['company' => '1'],
68+
['company' => new Can('update-company')]
69+
);
70+
71+
$this->assertTrue($v->fails());
72+
}
73+
74+
public function testValidationPasses()
75+
{
76+
$this->gate()->define('update-company', function ($user, $class, $model, $value) {
77+
$this->assertEquals(\App\Models\Company::class, $class);
78+
$this->assertInstanceOf(stdClass::class, $model);
79+
$this->assertEquals('1', $value);
80+
81+
return true;
82+
});
83+
84+
$v = new Validator(
85+
resolve('translator'),
86+
['company' => '1'],
87+
['company' => new Can('update-company', [\App\Models\Company::class, new stdClass])]
88+
);
89+
90+
$this->assertTrue($v->passes());
91+
}
92+
93+
/**
94+
* Get the Gate instance from the container.
95+
*
96+
* @return \Illuminate\Auth\Access\Gate
97+
*/
98+
protected function gate()
99+
{
100+
return $this->container->make(GateContract::class);
101+
}
102+
}

0 commit comments

Comments
 (0)