Repository: laravel/framework (php)

Issue:
How to add extra data on session guard recaller
### Discussed in https://github.com/laravel/framework/discussions/42067

<div type='discussions-op-text'>

<sup>Originally posted by **PaolaRuby** April 20, 2022</sup>
Only admit 3 segments, `id`,`token`,`hash`
https://github.com/laravel/framework/blob/b9203fca96960ef9cd8860cb4ec99d1279353a8d/src/Illuminate/Auth/Recaller.php#L80-L85
Is there a config for set a custom recaller with more segments?
Is there a way or a plugin for customize session recaller?</div>

If we create a custom session class extending `SessionGuard`, or we change the `remember me` cookie on login, we need a way to get the extra data from recaller
This is useful when you have a multi company login, or when needs an extra id/value to rebuild the session

Pull request description:
[9.x] Add custom segments on "remember me" for session rebuild
Closes #42271

Actually `Recaller` only admit 3 segments, `id`,`token`,`hash`,
If we create a custom session class extending `SessionGuard`, or we change the `remember me` cookie on login, we need a way to get the extra data from recaller, this is useful when you have a multi company login, or when needs an extra id/value to rebuild the session,

**Example:** company is a manyToMany relation with user, on login user pick wich company use for session, on "remember me" laravel rebuild the user session **but not the company**, of course i can create another cookie for rebuild the picked company or another company selector after session rebuild, but would be great if the recaller can handle that extra id/ids

**keeps same functionality, but adds option for more segments, really small not breaking change**

-----
https://github.com/laravel/framework/issues/42271#issuecomment-1118611261
>How are you setting these custom segments?

Login, custom session guard, or auth macro

>Why do you need them at all?

Rebuild the picked login company on session with remember me

>Why not store them in a database table associated with the user?

User can be logged on two or more companies at the same time(using different browsers, or with incognito mode, different computers, etc)

----


Patch:
diff --git a/src/Illuminate/Auth/Recaller.php b/src/Illuminate/Auth/Recaller.php
index 230d01a04b..4d96c82bc9 100644
--- a/src/Illuminate/Auth/Recaller.php
+++ b/src/Illuminate/Auth/Recaller.php
@@ -49,7 +49,7 @@ public function token()
      */
     public function hash()
     {
-        return explode('|', $this->recaller, 3)[2];
+        return explode('|', $this->recaller, 4)[2];
     }
 
     /**
@@ -81,6 +81,16 @@ protected function hasAllSegments()
     {
         $segments = explode('|', $this->recaller);
 
-        return count($segments) === 3 && trim($segments[0]) !== '' && trim($segments[1]) !== '';
+        return count($segments) >= 3 && trim($segments[0]) !== '' && trim($segments[1]) !== '';
+    }
+
+    /**
+     * Get the recaller's segments.
+     *
+     * @return array
+     */
+    public function segments()
+    {
+        return explode('|', $this->recaller);
     }
 }
diff --git a/src/Illuminate/Database/Concerns/ManagesTransactions.php b/src/Illuminate/Database/Concerns/ManagesTransactions.php
index 038e104779..9f3d4e2a5e 100644
--- a/src/Illuminate/Database/Concerns/ManagesTransactions.php
+++ b/src/Illuminate/Database/Concerns/ManagesTransactions.php
@@ -88,11 +88,7 @@ protected function handleTransactionException(Throwable $e, $currentAttempt, $ma
                 $this->getName(), $this->transactions
             );
 
-            throw new DeadlockException(
-                $e->getMessage(),
-                $e->getCode(),
-                $e->getPrevious()
-            );
+            throw new DeadlockException($e->getMessage(), (int) $e->getCode(), $e);
         }
 
         // If there was an exception we will rollback this transaction and then we
diff --git a/src/Illuminate/Database/DetectsLostConnections.php b/src/Illuminate/Database/DetectsLostConnections.php
index 16c8687483..c42e696ba7 100644
--- a/src/Illuminate/Database/DetectsLostConnections.php
+++ b/src/Illuminate/Database/DetectsLostConnections.php
@@ -57,6 +57,7 @@ protected function causedByLostConnection(Throwable $e)
             'The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.',
             'SQLSTATE[08006] [7] could not translate host name',
             'TCP Provider: Error code 0x274C',
+            'SQLSTATE[HY000] [2002] No such file or directory',
         ]);
     }
 }
diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php
index 4de065152d..2d42f88f0e 100755
--- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php
+++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php
@@ -310,6 +310,19 @@ public function create(array $attributes = [])
         });
     }
 
+    /**
+     * Create a new instance of the related model. Allow mass-assignment.
+     *
+     * @param  array  $attributes
+     * @return \Illuminate\Database\Eloquent\Model
+     */
+    public function forceCreate(array $attributes = [])
+    {
+        $attributes[$this->getForeignKeyName()] = $this->getParentKey();
+
+        return $this->related->forceCreate($attributes);
+    }
+
     /**
      * Create a Collection of new instances of the related model.
      *
diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php
index 6af83a4384..65c78db488 100755
--- a/src/Illuminate/Foundation/Application.php
+++ b/src/Illuminate/Foundation/Application.php
@@ -35,7 +35,7 @@ class Application extends Container implements ApplicationContract, CachesConfig
      *
      * @var string
      */
-    const VERSION = '9.11.0';
+    const VERSION = '9.12.0';
 
     /**
      * The base path for the Laravel installation.
diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php
index 8f9fb6b788..469b6f860f 100644
--- a/src/Illuminate/Http/Client/PendingRequest.php
+++ b/src/Illuminate/Http/Client/PendingRequest.php
@@ -20,6 +20,7 @@
 use Illuminate\Support\Traits\Macroable;
 use JsonSerializable;
 use Psr\Http\Message\MessageInterface;
+use Psr\Http\Message\RequestInterface;
 use RuntimeException;
 use Symfony\Component\VarDumper\VarDumper;
 
@@ -1106,15 +1107,21 @@ protected function sinkStubHandler($sink)
      *
      * @param  \GuzzleHttp\Psr7\RequestInterface  $request
      * @param  array  $options
-     * @return \Closure
+     * @return \GuzzleHttp\Psr7\RequestInterface
      */
     public function runBeforeSendingCallbacks($request, array $options)
     {
-        return tap($request, function ($request) use ($options) {
-            $this->beforeSendingCallbacks->each(function ($callback) use ($request, $options) {
-                call_user_func(
+        return tap($request, function (&$request) use ($options) {
+            $this->beforeSendingCallbacks->each(function ($callback) use (&$request, $options) {
+                $callbackResult = call_user_func(
                     $callback, (new Request($request))->withData($options['laravel_data']), $options, $this
                 );
+
+                if ($callbackResult instanceof RequestInterface) {
+                    $request = $callbackResult;
+                } elseif ($callbackResult instanceof Request) {
+                    $request = $callbackResult->toPsrRequest();
+                }
             });
         });
     }
diff --git a/src/Illuminate/Log/LogManager.php b/src/Illuminate/Log/LogManager.php
index 2934d6ffe1..2b0bf7a547 100644
--- a/src/Illuminate/Log/LogManager.php
+++ b/src/Illuminate/Log/LogManager.php
@@ -475,7 +475,7 @@ public function sharedContext()
     /**
      * Flush the shared context.
      *
-     * @return array
+     * @return $this
      */
     public function flushSharedContext()
     {
diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php
index 9a4ca19b54..386f6b4bd9 100644
--- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php
+++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php
@@ -581,6 +581,15 @@ public function validateDigitsBetween($attribute, $value, $parameters)
 
         $length = strlen((string) $value);
 
+        if (((string) $value) === '.') {
+            return false;
+        }
+
+        // Make sure there is not more than one dot...
+        if (($length - strlen(str_replace('.', '', (string) $value))) > 1) {
+            return false;
+        }
+
         return ! preg_match('/[^0-9.]/', $value)
                     && $length >= $parameters[0] && $length <= $parameters[1];
     }


Test patch:
diff --git a/tests/Database/DatabaseEloquentHasManyTest.php b/tests/Database/DatabaseEloquentHasManyTest.php
index 0d68f8fa7e..24c3025843 100755
--- a/tests/Database/DatabaseEloquentHasManyTest.php
+++ b/tests/Database/DatabaseEloquentHasManyTest.php
@@ -55,6 +55,15 @@ public function testCreateMethodProperlyCreatesNewModel()
         $this->assertEquals($created, $relation->create(['name' => 'taylor']));
     }
 
+    public function testForceCreateMethodProperlyCreatesNewModel()
+    {
+        $relation = $this->getRelation();
+        $created = $this->expectForceCreatedModel($relation, ['name' => 'taylor']);
+
+        $this->assertEquals($created, $relation->forceCreate(['name' => 'taylor']));
+        $this->assertEquals(1, $created->getAttribute('foreign_key'));
+    }
+
     public function testFindOrNewMethodFindsModel()
     {
         $relation = $this->getRelation();
@@ -304,6 +313,18 @@ protected function expectCreatedModel($relation, $attributes)
 
         return $model;
     }
+
+    protected function expectForceCreatedModel($relation, $attributes)
+    {
+        $attributes[$relation->getForeignKeyName()] = $relation->getParentKey();
+
+        $model = m::mock(Model::class);
+        $model->shouldReceive('getAttribute')->with($relation->getForeignKeyName())->andReturn($relation->getParentKey());
+
+        $relation->getRelated()->shouldReceive('forceCreate')->once()->with($attributes)->andReturn($model);
+
+        return $model;
+    }
 }
 
 class EloquentHasManyModelStub extends Model
diff --git a/tests/Database/DatabaseEloquentHasOneTest.php b/tests/Database/DatabaseEloquentHasOneTest.php
index c8d253d055..2a8712319b 100755
--- a/tests/Database/DatabaseEloquentHasOneTest.php
+++ b/tests/Database/DatabaseEloquentHasOneTest.php
@@ -129,6 +129,20 @@ public function testCreateMethodProperlyCreatesNewModel()
         $this->assertEquals($created, $relation->create(['name' => 'taylor']));
     }
 
+    public function testForceCreateMethodProperlyCreatesNewModel()
+    {
+        $relation = $this->getRelation();
+        $attributes = ['name' => 'taylor', $relation->getForeignKeyName() => $relation->getParentKey()];
+
+        $created = m::mock(Model::class);
+        $created->shouldReceive('getAttribute')->with($relation->getForeignKeyName())->andReturn($relation->getParentKey());
+
+        $relation->getRelated()->shouldReceive('forceCreate')->once()->with($attributes)->andReturn($created);
+
+        $this->assertEquals($created, $relation->forceCreate(['name' => 'taylor']));
+        $this->assertEquals(1, $created->getAttribute('foreign_key'));
+    }
+
     public function testRelationIsProperlyInitialized()
     {
         $relation = $this->getRelation();
diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php
index 330cb3b786..32b9852ac8 100644
--- a/tests/Http/HttpClientTest.php
+++ b/tests/Http/HttpClientTest.php
@@ -1578,4 +1578,26 @@ public function testItCanEnforceFaking()
 
         $this->factory->get('https://laravel.com');
     }
+
+    public function testItCanAddAuthorizationHeaderIntoRequestUsingBeforeSendingCallback()
+    {
+        $this->factory->fake();
+
+        $this->factory->beforeSending(function (Request $request) {
+            $requestLine = sprintf(
+                '%s %s HTTP/%s',
+                $request->toPsrRequest()->getMethod(),
+                $request->toPsrRequest()->getUri()->withScheme('')->withHost(''),
+                $request->toPsrRequest()->getProtocolVersion()
+            );
+
+            return $request->toPsrRequest()->withHeader('Authorization', 'Bearer '.$requestLine);
+        })->get('http://foo.com/json');
+
+        $this->factory->assertSent(function (Request $request) {
+            return
+                $request->url() === 'http://foo.com/json' &&
+                $request->hasHeader('Authorization', 'Bearer GET /json HTTP/1.1');
+        });
+    }
 }
diff --git a/tests/Log/LogManagerTest.php b/tests/Log/LogManagerTest.php
index 9b5abd1724..3b7eb5bb62 100755
--- a/tests/Log/LogManagerTest.php
+++ b/tests/Log/LogManagerTest.php
@@ -572,4 +572,17 @@ public function testItMergesSharedContextRatherThanReplacing()
             'invocation-start' => 1651800456,
         ], $manager->sharedContext());
     }
+
+    public function testFlushSharedContext()
+    {
+        $manager = new LogManager($this->app);
+
+        $manager->shareContext($context = ['foo' => 'bar']);
+
+        $this->assertSame($context, $manager->sharedContext());
+
+        $manager->flushSharedContext();
+
+        $this->assertEmpty($manager->sharedContext());
+    }
 }
diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php
index bdfc0977bb..a95d1fcb6c 100755
--- a/tests/Validation/ValidationValidatorTest.php
+++ b/tests/Validation/ValidationValidatorTest.php
@@ -2343,6 +2343,24 @@ public function testValidateDigits()
 
         $v = new Validator($trans, ['foo' => '0.9876'], ['foo' => 'digits_between:1,5']);
         $this->assertTrue($v->fails());
+
+        $v = new Validator($trans, ['foo' => '1..2'], ['foo' => 'digits_between:1,10']);
+        $this->assertTrue($v->fails());
+
+        $v = new Validator($trans, ['foo' => '123.456.789'], ['foo' => 'digits_between:1,10']);
+        $this->assertTrue($v->fails());
+
+        $v = new Validator($trans, ['foo' => '...'], ['foo' => 'digits_between:1,10']);
+        $this->assertTrue($v->fails());
+
+        $v = new Validator($trans, ['foo' => '.'], ['foo' => 'digits_between:1,10']);
+        $this->assertTrue($v->fails());
+
+        $v = new Validator($trans, ['foo' => '.2'], ['foo' => 'digits_between:0,10']);
+        $this->assertTrue($v->passes());
+
+        $v = new Validator($trans, ['foo' => '2.'], ['foo' => 'digits_between:1,10']);
+        $this->assertTrue($v->passes());
     }
 
     public function testValidateSize()