Repository: laravel/framework (php)

Issue:
assertSoftDeleted() and assertNotSoftDeleted() discard $data argument value when first argument is a model
<!-- DO NOT THROW THIS AWAY -->
<!-- Fill out the FULL versions with patch versions -->

- Laravel Version: 8.71.0
- PHP Version: 8.0.2
- Database Driver & Version: MySQL 8.0

### Description:
The testing helpers `assertSoftDeleted()` and `assertNotSoftDeleted()` both accept either a table name provided as a string, or a model that implements the `SoftDeletes` trait. When a model is provided these functions automatically filter the query to the table for the model, as well as looking for the record with the provided primary key.

However, when a model is provided any values provided in the second argument, `$data`, are discarded when the function recursively calls itself. As a result, what appears to be a valid test case is actually not executed with the expected behavior:

```
// Assume a database table (example_table) has the following record:
id  | col_a | col_b | deleted_at
1   | foo     | null     | 2021-11-16 00:00:00

// Assume we have a "$model" variable that exists for this record.
// Now, in a test we add the following assertion:

$this->assertSoftDeleted($model, ['col_b' => 'bar']);
```

The example above will pass, even though the database does _not_ contain a record for that model where `col_b` is equal to `bar`. However, if you give that assertion the following code, it _does_ fail as expected:

```
$this->assertSoftDeleted('example_table', ['id' => 1, 'col_b' => 'bar']);
```

The code responsible for these functions is here:
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php#L90-L132

The specific chunk responsible for dropping the `$data` parameter is here:
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php#L123-L125

Pull request description:
[8.x] Fix assertSoftDeleted & assertNotSoftDeleted
This PR fixes an issue where `$data` wasn't passed along properly as a where clause for the `assertSoftDeleted` & `assertNotSoftDeleted` methods. By passing the data properly, more granular assertions can be made.

Fixes #39667


Patch:
diff --git a/src/Illuminate/Auth/Notifications/ResetPassword.php b/src/Illuminate/Auth/Notifications/ResetPassword.php
index 585c35f529..f1c13f4d72 100644
--- a/src/Illuminate/Auth/Notifications/ResetPassword.php
+++ b/src/Illuminate/Auth/Notifications/ResetPassword.php
@@ -59,13 +59,11 @@ public function via($notifiable)
      */
     public function toMail($notifiable)
     {
-        $resetUrl = $this->resetUrl($notifiable);
-
         if (static::$toMailCallback) {
-            return call_user_func(static::$toMailCallback, $notifiable, $this->token, $resetUrl);
+            return call_user_func(static::$toMailCallback, $notifiable, $this->token);
         }
 
-        return $this->buildMailMessage($resetUrl);
+        return $this->buildMailMessage($this->resetUrl($notifiable));
     }
 
     /**
diff --git a/src/Illuminate/Cache/Repository.php b/src/Illuminate/Cache/Repository.php
index e34c285729..2a4f23af1c 100755
--- a/src/Illuminate/Cache/Repository.php
+++ b/src/Illuminate/Cache/Repository.php
@@ -373,7 +373,7 @@ public function forever($key, $value)
      * Get an item from the cache, or execute the given Closure and store the result.
      *
      * @param  string  $key
-     * @param  \DateTimeInterface|\DateInterval|int|null  $ttl
+     * @param  \Closure|\DateTimeInterface|\DateInterval|int|null  $ttl
      * @param  \Closure  $callback
      * @return mixed
      */
@@ -388,7 +388,7 @@ public function remember($key, $ttl, Closure $callback)
             return $value;
         }
 
-        $this->put($key, $value = $callback(), $ttl);
+        $this->put($key, $value = $callback(), value($ttl));
 
         return $value;
     }
diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php
index 9d9624e3a9..8d9a875d7b 100755
--- a/src/Illuminate/Validation/Validator.php
+++ b/src/Illuminate/Validation/Validator.php
@@ -247,6 +247,7 @@ class Validator implements ValidatorContract
         'Prohibited',
         'ProhibitedIf',
         'ProhibitedUnless',
+        'Prohibits',
         'Same',
         'Unique',
     ];


Test patch:
diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php
index 80d2f06377..8ccd7e2f39 100644
--- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php
+++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php
@@ -99,7 +99,12 @@ protected function assertDeleted($table, array $data = [], $connection = null)
     protected function assertSoftDeleted($table, array $data = [], $connection = null, $deletedAtColumn = 'deleted_at')
     {
         if ($this->isSoftDeletableModel($table)) {
-            return $this->assertSoftDeleted($table->getTable(), [$table->getKeyName() => $table->getKey()], $table->getConnectionName(), $table->getDeletedAtColumn());
+            return $this->assertSoftDeleted(
+                $table->getTable(),
+                array_merge($data, [$table->getKeyName() => $table->getKey()]),
+                $table->getConnectionName(),
+                $table->getDeletedAtColumn()
+            );
         }
 
         $this->assertThat(
@@ -121,7 +126,12 @@ protected function assertSoftDeleted($table, array $data = [], $connection = nul
     protected function assertNotSoftDeleted($table, array $data = [], $connection = null, $deletedAtColumn = 'deleted_at')
     {
         if ($this->isSoftDeletableModel($table)) {
-            return $this->assertNotSoftDeleted($table->getTable(), [$table->getKeyName() => $table->getKey()], $table->getConnectionName(), $table->getDeletedAtColumn());
+            return $this->assertNotSoftDeleted(
+                $table->getTable(),
+                array_merge($data, [$table->getKeyName() => $table->getKey()]),
+                $table->getConnectionName(),
+                $table->getDeletedAtColumn()
+            );
         }
 
         $this->assertThat(
diff --git a/tests/Cache/CacheRepositoryTest.php b/tests/Cache/CacheRepositoryTest.php
index 6132c9f26c..86064250e8 100755
--- a/tests/Cache/CacheRepositoryTest.php
+++ b/tests/Cache/CacheRepositoryTest.php
@@ -113,6 +113,19 @@ public function testRememberMethodCallsPutAndReturnsDefault()
             return 'qux';
         });
         $this->assertSame('qux', $result);
+
+        /*
+         * Use a callable...
+         */
+        $repo = $this->getRepository();
+        $repo->getStore()->shouldReceive('get')->once()->andReturn(null);
+        $repo->getStore()->shouldReceive('put')->once()->with('foo', 'bar', 10);
+        $result = $repo->remember('foo', function () {
+            return 10;
+        }, function () {
+            return 'bar';
+        });
+        $this->assertSame('bar', $result);
     }
 
     public function testRememberForeverMethodCallsForeverAndReturnsDefault()
diff --git a/tests/Foundation/FoundationInteractsWithDatabaseTest.php b/tests/Foundation/FoundationInteractsWithDatabaseTest.php
index 259e819d08..c29074849b 100644
--- a/tests/Foundation/FoundationInteractsWithDatabaseTest.php
+++ b/tests/Foundation/FoundationInteractsWithDatabaseTest.php
@@ -241,18 +241,19 @@ public function testAssertSoftDeletedInDatabaseDoesNotFindModelWithCustomColumnR
         $this->expectException(ExpectationFailedException::class);
         $this->expectExceptionMessage('The table is empty.');
 
-        $this->data = ['id' => 1];
+        $model = new CustomProductStub(['id' => 1, 'name' => 'Laravel']);
+        $this->data = ['id' => 1, 'name' => 'Tailwind'];
 
         $builder = $this->mockCountBuilder(0, 'trashed_at');
 
         $builder->shouldReceive('get')->andReturn(collect());
 
-        $this->assertSoftDeleted(new CustomProductStub($this->data));
+        $this->assertSoftDeleted($model, ['name' => 'Tailwind']);
     }
 
     public function testAssertNotSoftDeletedInDatabaseFindsResults()
     {
-        $builder = $this->mockCountBuilder(1);
+        $this->mockCountBuilder(1);
 
         $this->assertNotSoftDeleted($this->table, $this->data);
     }
@@ -307,13 +308,14 @@ public function testAssertNotSoftDeletedInDatabaseDoesNotFindModelWithCustomColu
         $this->expectException(ExpectationFailedException::class);
         $this->expectExceptionMessage('The table is empty.');
 
-        $this->data = ['id' => 1];
+        $model = new CustomProductStub(['id' => 1, 'name' => 'Laravel']);
+        $this->data = ['id' => 1, 'name' => 'Tailwind'];
 
         $builder = $this->mockCountBuilder(0, 'trashed_at');
 
         $builder->shouldReceive('get')->andReturn(collect());
 
-        $this->assertNotSoftDeleted(new CustomProductStub($this->data));
+        $this->assertNotSoftDeleted($model, ['name' => 'Tailwind']);
     }
 
     public function testAssertExistsPassesWhenFindsResults()
diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php
index edb9e4cdeb..99880973fc 100755
--- a/tests/Validation/ValidationValidatorTest.php
+++ b/tests/Validation/ValidationValidatorTest.php
@@ -1509,6 +1509,17 @@ public function testProhibits()
         $v = new Validator($trans, ['email' => 'foo', 'emails' => 'bar', 'email_address' => 'baz'], ['email' => 'prohibits:emails,email_address']);
         $this->assertFalse($v->passes());
         $this->assertSame('The email field prohibits emails / email address being present.', $v->messages()->first('email'));
+
+        $trans = $this->getIlluminateArrayTranslator();
+        $v = new Validator($trans, [
+            'foo' => [
+                ['email' => 'foo', 'emails' => 'foo'],
+                ['emails' => 'foo'],
+            ],
+        ], ['foo.*.email' => 'prohibits:foo.*.emails']);
+        $this->assertFalse($v->passes());
+        $this->assertTrue($v->messages()->has('foo.0.email'));
+        $this->assertFalse($v->messages()->has('foo.1.email'));
     }
 
     public function testFailedFileUploads()