Repository: laravel/framework (php)

Issue:
The `size/max/min` validation rules perform incorrect validation with `decimal` rule
<!-- DO NOT THROW THIS AWAY -->
<!-- Fill out the FULL versions with patch versions -->

- Laravel Version: 9.26.1
- PHP Version: 8.1.13
- Database Driver & Version: Postgres 14

### Description:
Hello,

The docs for the `size` validation rule show that when used with `integer` or `numeric` it validates against an integer value:

> For numeric data, value corresponds to a given integer value (the attribute must also have the numeric or integer rule).

However, when `size` used with the new `decimal` rule it is using string validation (that is, number of characters). The expected behaviour is that `size/min/max` would be the same with `decimal` as with `numeric`.

### Steps To Reproduce:

```
Validator::make(['decimal' => 250.0], [
  'decimal' => ['required', 'decimal:0,2', 'max:200'], // passes
  'decimal' => ['required', 'numeric', 'max:200'], // fails
  ])->validate()
```

<!-- If possible, please provide a GitHub repository to demonstrate your issue -->
<!-- laravel new bug-report --github="--public" -->


Pull request:
[9.x] Add Decimal to list of Numeric rules
<!--
Please only send a pull request to branches that are currently supported: https://laravel.com/docs/releases#support-policy 

If you are unsure which branch your pull request should be sent to, please read: https://laravel.com/docs/contributions#which-branch

Pull requests without a descriptive title, thorough description, or tests will be closed.

In addition, please describe the benefit to end users; the reasons it does not break any existing features; how it makes building web applications easier, etc.
-->

Fixes #45532

Added `Decimal` to list of numeric rules, added tests.



Patch:
diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php
index 0f2f299559..280f4d8473 100755
--- a/src/Illuminate/Container/Container.php
+++ b/src/Illuminate/Container/Container.php
@@ -648,7 +648,25 @@ public function wrap(Closure $callback, array $parameters = [])
      */
     public function call($callback, array $parameters = [], $defaultMethod = null)
     {
-        return BoundMethod::call($this, $callback, $parameters, $defaultMethod);
+        $pushedToBuildStack = false;
+
+        if (is_array($callback) && ! in_array(
+            $className = (is_string($callback[0]) ? $callback[0] : get_class($callback[0])),
+            $this->buildStack,
+            true
+        )) {
+            $this->buildStack[] = $className;
+
+            $pushedToBuildStack = true;
+        }
+
+        $result = BoundMethod::call($this, $callback, $parameters, $defaultMethod);
+
+        if ($pushedToBuildStack) {
+            array_pop($this->buildStack);
+        }
+
+        return $result;
     }
 
     /**
diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php
index 407d60d8ba..8a552fae62 100755
--- a/src/Illuminate/Validation/Validator.php
+++ b/src/Illuminate/Validation/Validator.php
@@ -274,7 +274,7 @@ class Validator implements ValidatorContract
      *
      * @var string[]
      */
-    protected $numericRules = ['Numeric', 'Integer'];
+    protected $numericRules = ['Numeric', 'Integer', 'Decimal'];
 
     /**
      * The current placeholder for dots in rule keys.


Test patch:
diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php
index f16cd0e6dc..53cf9b6713 100755
--- a/tests/Container/ContainerTest.php
+++ b/tests/Container/ContainerTest.php
@@ -665,6 +665,21 @@ public function testContainerCanResolveClasses()
         $this->assertInstanceOf(ContainerConcreteStub::class, $class);
     }
 
+    public function testMethodLevelContextualBinding()
+    {
+        $container = new Container;
+
+        $container->bind(IContainerContractStub::class, ContainerImplementationStubTwo::class);
+
+        $container->when(ContainerContextualBindingCallTarget::class)
+                ->needs(IContainerContractStub::class)
+                ->give(ContainerImplementationStub::class);
+
+        $result = $container->call([new ContainerContextualBindingCallTarget, 'work']);
+
+        $this->assertInstanceOf(ContainerImplementationStub::class, $result);
+    }
+
     // public function testContainerCanCatchCircularDependency()
     // {
     //     $this->expectException(\Illuminate\Contracts\Container\CircularDependencyException::class);
@@ -783,3 +798,15 @@ public function __construct(ContainerConcreteStub $concrete, $something)
         $this->something = $something;
     }
 }
+
+class ContainerContextualBindingCallTarget
+{
+    public function __construct()
+    {
+    }
+
+    public function work(IContainerContractStub $stub)
+    {
+        return $stub;
+    }
+}
diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php
index e4140862f1..645efb1f23 100755
--- a/tests/Validation/ValidationValidatorTest.php
+++ b/tests/Validation/ValidationValidatorTest.php
@@ -2583,6 +2583,18 @@ public function testValidateSize()
         $v = new Validator($trans, ['foo' => '3'], ['foo' => 'Numeric|Size:3']);
         $this->assertTrue($v->passes());
 
+        $v = new Validator($trans, ['foo' => '123'], ['foo' => 'Decimal:0|Size:3']);
+        $this->assertFalse($v->passes());
+
+        $v = new Validator($trans, ['foo' => '3'], ['foo' => 'Decimal:0|Size:3']);
+        $this->assertTrue($v->passes());
+
+        $v = new Validator($trans, ['foo' => '123'], ['foo' => 'Integer|Size:3']);
+        $this->assertFalse($v->passes());
+
+        $v = new Validator($trans, ['foo' => '3'], ['foo' => 'Integer|Size:3']);
+        $this->assertTrue($v->passes());
+
         $v = new Validator($trans, ['foo' => [1, 2, 3]], ['foo' => 'Array|Size:3']);
         $this->assertTrue($v->passes());
 
@@ -2684,6 +2696,17 @@ public function testValidateMin()
         $v = new Validator($trans, ['foo' => '20'], ['foo' => 'Min:3']);
         $this->assertFalse($v->passes());
 
+        // an equal value qualifies.
+        $v = new Validator($trans, ['foo' => '3'], ['foo' => 'Decimal:0|Min:3']);
+        $this->assertTrue($v->passes());
+
+        $v = new Validator($trans, ['foo' => '2'], ['foo' => 'Decimal:0|Min:3']);
+        $this->assertFalse($v->passes());
+
+        // '2.001' is considered as a float when the "Numeric" rule exists.
+        $v = new Validator($trans, ['foo' => '2.001'], ['foo' => 'Decimal:0,3|Min:3']);
+        $this->assertFalse($v->passes());
+
         $v = new Validator($trans, ['foo' => '5'], ['foo' => 'Numeric|Min:3']);
         $this->assertTrue($v->passes());
 
@@ -2728,6 +2751,17 @@ public function testValidateMax()
         $v = new Validator($trans, ['foo' => '2.001'], ['foo' => 'Max:3']);
         $this->assertFalse($v->passes());
 
+        $v = new Validator($trans, ['foo' => '211'], ['foo' => 'Decimal:0|Max:100']);
+        $this->assertFalse($v->passes());
+
+        // an equal value qualifies.
+        $v = new Validator($trans, ['foo' => '3'], ['foo' => 'Decimal:0|Max:3']);
+        $this->assertTrue($v->passes());
+
+        // '2.001' is considered as a float when the "Numeric" rule exists.
+        $v = new Validator($trans, ['foo' => '2.001'], ['foo' => 'Decimal:0,3|Max:3']);
+        $this->assertTrue($v->passes());
+
         $v = new Validator($trans, ['foo' => '22'], ['foo' => 'Numeric|Max:33']);
         $this->assertTrue($v->passes());