【问题标题】:Getting "Trying to get property 'subtotal' of non-object" when using findOrFail使用 findOrFail 时获取“尝试获取非对象的属性‘小计’”
【发布时间】:2020-05-11 19:37:24
【问题描述】:

我在服务中有以下代码:

public function add(array $data) : Order
    {
        // retrieve item data
        $item       = MenuItem::findOrFail($data[OrderItem::ORDER_ITEM_ITEM_ID]);
        $quantity   = $data[OrderItem::ORDER_ITEM_QUANTITY];
        $order_no   = $data[Order::ORDER_NO] ?? null;
        $session_id = $data[Order::ORDER_SESSION_ID];

        $order = $order_no ? Order::findOrFail($order_no) : $this->createOrder([Order::ORDER_SESSION_ID => $session_id]);

        $order_item = $this->order_item->createOrderItem($order->order_no, $item, $quantity);

        // update order total
        $order->subtotal += $order_item->subtotal;
        $order->total    += $order_item->subtotal;
        $order->update();

        return $order;
    }

运行测试时我得到:

获取“试图获取非对象的属性'小计'”

检索到的对象不为空,否则会失败。但小计和总计都不属于对象。如果我打印对象,我会得到除这两个之外的其他字段。它们都在我的$fillable 数组中,我在迁移中定义了一个->default(0);

发生了什么事?

编辑

PHP 单元输出如下:

PHPUnit 8.5.0 by Sebastian Bergmann and contributors.

array:5 [
  "message" => "Trying to get property 'subtotal' of non-object"
  "exception" => "ErrorException"
  "file" => "/Users/bigweld/Sites/restaurantbe/app/Services/OrderService.php"
  "line" => 110
  "trace" => array:33 [
    0 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/app/Services/OrderService.php"
      "line" => 110
      "function" => "handleError"
      "class" => "Illuminate\Foundation\Bootstrap\HandleExceptions"
      "type" => "->"
    ]
    1 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/app/Http/Controllers/API/OrderController.php"
      "line" => 57
      "function" => "add"
      "class" => "App\Services\OrderService"
      "type" => "->"
    ]
    2 => array:3 [
      "function" => "add"
      "class" => "App\Http\Controllers\API\OrderController"
      "type" => "->"
    ]
    3 => array:3 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Controller.php"
      "line" => 54
      "function" => "call_user_func_array"
    ]
    4 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php"
      "line" => 45
      "function" => "callAction"
      "class" => "Illuminate\Routing\Controller"
      "type" => "->"
    ]
    5 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Route.php"
      "line" => 219
      "function" => "dispatch"
      "class" => "Illuminate\Routing\ControllerDispatcher"
      "type" => "->"
    ]
    6 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Route.php"
      "line" => 176
      "function" => "runController"
      "class" => "Illuminate\Routing\Route"
      "type" => "->"
    ]
    7 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Router.php"
      "line" => 681
      "function" => "run"
      "class" => "Illuminate\Routing\Route"
      "type" => "->"
    ]
    8 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php"
      "line" => 130
      "function" => "Illuminate\Routing\{closure}"
      "class" => "Illuminate\Routing\Router"
      "type" => "->"
    ]
    9 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php"
      "line" => 105
      "function" => "Illuminate\Pipeline\{closure}"
      "class" => "Illuminate\Pipeline\Pipeline"
      "type" => "->"
    ]
    10 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Router.php"
      "line" => 683
      "function" => "then"
      "class" => "Illuminate\Pipeline\Pipeline"
      "type" => "->"
    ]
    11 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Router.php"
      "line" => 658
      "function" => "runRouteWithinStack"
      "class" => "Illuminate\Routing\Router"
      "type" => "->"
    ]
    12 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Router.php"
      "line" => 624
      "function" => "runRoute"
      "class" => "Illuminate\Routing\Router"
      "type" => "->"
    ]
    13 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Router.php"
      "line" => 613
      "function" => "dispatchToRoute"
      "class" => "Illuminate\Routing\Router"
      "type" => "->"
    ]
    14 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php"
      "line" => 177
      "function" => "dispatch"
      "class" => "Illuminate\Routing\Router"
      "type" => "->"
    ]
    15 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php"
      "line" => 130
      "function" => "Illuminate\Foundation\Http\{closure}"
      "class" => "Illuminate\Foundation\Http\Kernel"
      "type" => "->"
    ]
    16 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php"
      "line" => 105
      "function" => "Illuminate\Pipeline\{closure}"
      "class" => "Illuminate\Pipeline\Pipeline"
      "type" => "->"
    ]
    17 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php"
      "line" => 152
      "function" => "then"
      "class" => "Illuminate\Pipeline\Pipeline"
      "type" => "->"
    ]
    18 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php"
      "line" => 117
      "function" => "sendRequestThroughRouter"
      "class" => "Illuminate\Foundation\Http\Kernel"
      "type" => "->"
    ]
    19 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php"
      "line" => 434
      "function" => "handle"
      "class" => "Illuminate\Foundation\Http\Kernel"
      "type" => "->"
    ]
    20 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php"
      "line" => 406
      "function" => "call"
      "class" => "Illuminate\Foundation\Testing\TestCase"
      "type" => "->"
    ]
    21 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/tests/Feature/OrdersTest.php"
      "line" => 428
      "function" => "json"
      "class" => "Illuminate\Foundation\Testing\TestCase"
      "type" => "->"
    ]
    22 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestCase.php"
      "line" => 1408
      "function" => "when_adding_the_same_item_twice_to_an_empty_order_then_only_one_order_item_is_created_but_its_quantity_increases"
      "class" => "Tests\Feature\OrdersTest"
      "type" => "->"
    ]
    23 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestCase.php"
      "line" => 1028
      "function" => "runTest"
      "class" => "PHPUnit\Framework\TestCase"
      "type" => "->"
    ]
    24 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestResult.php"
      "line" => 691
      "function" => "runBare"
      "class" => "PHPUnit\Framework\TestCase"
      "type" => "->"
    ]
    25 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestCase.php"
      "line" => 756
      "function" => "run"
      "class" => "PHPUnit\Framework\TestResult"
      "type" => "->"
    ]
    26 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestSuite.php"
      "line" => 597
      "function" => "run"
      "class" => "PHPUnit\Framework\TestCase"
      "type" => "->"
    ]
    27 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestSuite.php"
      "line" => 597
      "function" => "run"
      "class" => "PHPUnit\Framework\TestSuite"
      "type" => "->"
    ]
    28 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestSuite.php"
      "line" => 597
      "function" => "run"
      "class" => "PHPUnit\Framework\TestSuite"
      "type" => "->"
    ]
    29 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/TextUI/TestRunner.php"
      "line" => 621
      "function" => "run"
      "class" => "PHPUnit\Framework\TestSuite"
      "type" => "->"
    ]
    30 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/TextUI/Command.php"
      "line" => 200
      "function" => "doRun"
      "class" => "PHPUnit\TextUI\TestRunner"
      "type" => "->"
    ]
    31 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/TextUI/Command.php"
      "line" => 159
      "function" => "run"
      "class" => "PHPUnit\TextUI\Command"
      "type" => "->"
    ]
    32 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/phpunit"
      "line" => 61
      "function" => "main"
      "class" => "PHPUnit\TextUI\Command"
      "type" => "::"
    ]
  ]
]

【问题讨论】:

  • createOrderItem 实际返回了什么?鉴于这是一个测试,你是在模拟它并返回一个已知的结果吗?
  • createOrderItem 返回OrderItem 类型的对象。当我尝试访问$order->subtotal$order->total 时,错误消息不是来自OrderItem,而是来自Order$this->createOrder() 返回一个OrderOrder::findOrFail() 类型的obj,实际上返回了一个对象,但是没有subtotal 也没有total 属性
  • 好的,我错过了那一行的+=,但小计在那一行的两个对象上,所以我不确定是指哪个。你能确认这两个项目都是对象吗?
  • 我相信我已经找到了问题所在。错误输出将我指向与 Order 相关的类。在您提到createOrderItem 之后,我检查了OrderItem 的服务并在createOrderItem 中看到以下内容:``` $order_item->quantity += $qty; $order_item->小计 += ($item->price * $qty);返回 $order_item->update(); ``` 显然问题出在 return 语句中。我修改为更新上面的一行并简单地返回$order_item,它似乎正在工作。谢谢你强迫我去那里检查
  • @Jason 我刚刚添加了错误输出,您可以看到它从未在OrderItemService 中抱怨过createOrderItem

标签: php laravel eloquent laravel-6


【解决方案1】:

根据我们在 cmets 中的对话,您在函数中遇到的一个问题,并因此导致您的测试出现问题,是每次都无法保证结果的依赖关系。也就是说,您在 add 函数中调用另一个服务并不能保证提供特定的输出。

这样想你的测试。

  • 我从一个包含零项和零小计的订单开始
  • 我想在该订单中添加商品
  • 我想以包含一件商品和 10 美元小计的订单结束

在这个测试中,您不关心如何创建订单项,您只关心它创建的,具有特定值,因此当它添加到您的订单中,最终结果是您所期望的。

在这种情况下,您希望$this->order_item 成为一个模拟对象,然后您可以模拟对createOrderItem 的调用以返回一个已知的订单对象。向您展示一个完整的解决方案有点困难,因为它可能涉及一些架构更改,但您的测试将类似于:

public function it_adds_an_item_to_an_order()
{
  $orderItem = Mock(OrderItem::class); // Use whatever mocking lib is available
  $orderitem->shouldReceive('createOrderItem').andReturn(new OrderItem([$subtotal=>10.00]));
  $orderService = new OrderService($orderItem);

  $order = $orderService->add($data);

  $this->assertNotNull($order);
  $this->assertEquals(10.00, $order->subtotal);
}

希望这对您有所帮助。

【讨论】:

  • 我喜欢这次谈话的进展。我很欣赏这个提示,因为我在单元测试领域相对较新,这将是我使用 TDD 的第一个项目之一。我对模拟的东西不是很熟悉,我目前的测试更像是集成测试。我已经对简单的订单创建进行了测试(将一件商品添加到订单中),这个特定的测试是将相同的商品添加到同一订单中。为了在我的Order 服务中使用order_item 服务,我通过构造函数将它与Order 模型一起注入,这对我来说不是很有说服力
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-02
  • 2015-09-30
  • 1970-01-01
  • 2016-04-15
  • 1970-01-01
  • 2021-02-26
相关资源
最近更新 更多