php - Laravel 5.1 Eloquent ORM随机返回不正确的关系-*重大更新*

标签 php laravel laravel-5 eloquent laravel-5.1

我有一个Laravel应用,可为流量适中的电子商务网站提供支持。该网站允许人们通过前端下订单,但它也具有后端功能,可通过调用中心通过电话接收订单。

订单与客户有关,客户可以选择是用户-用户是登录前端的用户。没有用户帐户的客户只能通过调用中心获得订单来创建。

我遇到的问题很奇怪,我相信可能是某种Laravel错误。

它仅偶尔发生,但实际上是,当通过调用中心为没有用户帐户的客户下订单时,订单确认会发送给随机用户-据我所知,实际上是随机的,尽管数据没有关系,但只是从数据库中拔出。

这些是项目中模型的相关部分:

class Order extends Model
{
    public function customer()
    {
        return $this->belongsTo('App\Customer');
    }
}

class Customer extends Model
{
    public function orders()
    {
        return $this->hasMany('App\Order');
    }

    public function user()
    {
        return $this->belongsTo('App\User');
    }
}

class User extends Model
{ 
    public function customer()
    {
        return $this->hasOne('App\Customer');
    }
}

这些是上述数据库的迁移(为简便起见进行了编辑):
   Schema::create('users', function (Blueprint $table) {
        $table->increments('id');
        $table->string('first_name');
        $table->string('last_name');
        $table->string('email')->unique();
        $table->string('password', 60);
        $table->boolean('active');
        $table->rememberToken();
        $table->timestamps();
        $table->softDeletes();
    });

    Schema::create('customers', function(Blueprint $table)
    {
        $table->increments('id');
        $table->integer('user_id')->nullable->index();
        $table->string('first_name');
        $table->string('last_name');
        $table->string('telephone')->nullable();
        $table->string('mobile')->nullable();
        $table->timestamps();
        $table->softDeletes();
    });

    Schema::create('orders', function(Blueprint $table)
    {
        $table->increments('id');
        $table->integer('payment_id')->nullable()->index();
        $table->integer('customer_id')->index();
        $table->integer('staff_id')->nullable()->index();
        $table->decimal('total', 10, 2);
        $table->timestamps();
        $table->softDeletes();
    });

发送订单确认的逻辑在事件处理程序中,该事件处理程序在订单付款后被触发。

这是OrderSuccess事件(为简洁起见进行了编辑):
namespace App\Events;

use App\Events\Event;
use App\Order;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;


class OrderSuccess extends Event
{
    use SerializesModels;

    public $order;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(Order $order)
    {
        $this->order = $order;
    }
}

可以看出,此事件传递了一个Order模型对象。

这是事件处理程序(为简洁起见进行了编辑):
/**
 * Handle the event.
 *
 * @param  OrderSuccess  $event
 * @return void
 */
public function handle(OrderSuccess $event)
{
    // set order to paid
    $order = $event->order;
    $order->paid = date('Y-m-d H:i:s');
    $order->save();

    if(!is_null($order->customer->user)) {

        App_log::add('customer_order_success_email_sent', 'Handlers\Events\OrderSuccessProcess\handle', $order->id, print_r($order->customer, true).PHP_EOL.print_r($order->customer->user, true));

        // email the user the order confirmation
        Mail::send('emails.order_success', ['order' => $order], function($message) use ($order)
        {
            $message->to($order->customer->user->email, $order->customer->first_name.' '.$order->customer->last_name)->subject('Order #'.$order->id.' confirmation');
        });
    }

}

进行检查以查看$order->customer->user对象是否不为null,如果为true,则发送订单确认。如果为null(通常为null),则不发送确认。

从上面可以看出,我添加了一个日志来记录发送电子邮件时的对象。这是一个错误示例(为简洁起见,也被删节了):
App\Customer Object
(
[attributes:protected] => Array
    (
        [id] => 10412
        [user_id] => 
        [first_name] => Joe
        [last_name] => Bloggs
        [telephone] => 0123456789
        [created_at] => 2015-09-14 13:09:45
        [updated_at] => 2015-10-24 05:00:01
        [deleted_at] => 
    )

[relations:protected] => Array
    (
        [user] => App\User Object
            (
                [attributes:protected] => Array
                    (
                        [id] => 1206
                        [email] => johndoe@whoknows.com
                        [password] => hashed
                        [remember_token] => 
                        [created_at] => 2015-09-19 09:47:16
                        [updated_at] => 2015-09-19 09:47:16
                        [deleted_at] => 
                    )
            )

    )

[morphClass:protected] => 
[exists] => 1
[wasRecentlyCreated] => 
[forceDeleting:protected] => 
)

App\User Object
(
[attributes:protected] => Array
    (
        [id] => 1206
        [email] => johndoe@whoknows.com
        [password] => hashed
        [remember_token] => 
        [created_at] => 2015-09-19 09:47:16
        [updated_at] => 2015-09-19 09:47:16
        [deleted_at] => 
    )

[morphClass:protected] => 
[exists] => 1
[wasRecentlyCreated] => 
[forceDeleting:protected] => 
)

如您所见,Customer没有user_id,但是Laravel返回了User对象。

而且,如果我手动触发完全相同的OrderSuccess事件,则以上内容不可重现-它不会发送电子邮件,也不会加载User对象。

正如我之前说过的,此问题很少发生-对于没有用户帐户的客户,每天平均有40个左右的订单通过调用中心进行,而突出显示的问题可能仅一周发生一次或两次。

我对Laravel不太了解,可能是这里的问题-是某种形式的模型缓存,Eloquent ORM的问题还是系统中的其他gremlin?

任何想法表示赞赏-如果它似乎是某种形式的错误,我可以在Laravel github问题跟踪器中发布此问题。

更新关于提出的一些答案/评论,我试图删除任何潜在的 Eloquent ORM问题,以手动方式检索数据,如下所示:
$customer = Customer::find($order->customer_id);
$user = User::find($customer->user_id);

if(!is_null($user)) {
    // send email and log actions etc
}

上面的代码仍然会产生相同的随机结果-即使客户没有user_id(在这种情况下为NULL),也会检索不相关的用户。

更新2 由于第一次更新没有任何帮助,因此我恢复使用原始的Eloequent方法。为了尝试另一种解决方案,我将事件代码从事件处理程序中取出,并将其放置在 Controller 中-之前我是使用Event::fire(new OrderSuccess ($order));通过OrderSuccess事件触发的,而我注释掉了这一行,只是将事件处理程序代码放置在了 Controller 中方法:
$order = Order::find($order_id);

//Event::fire(new OrderSuccess ($order));

// code from the above event handler
$order->paid = date('Y-m-d H:i:s');
$order->save();

if(!is_null($order->customer->user)) {

    App_log::add('customer_order_success_email_sent', 'Handlers\Events\OrderSuccessProcess\handle', $order->id, print_r($order->customer, true).PHP_EOL.print_r($order->customer->user, true));

    // email the user the order confirmation
    Mail::send('emails.order_success', ['order' => $order], function($message) use ($order)
    {
        $message->to($order->customer->user->email, $order->customer->first_name.' '.$order->customer->last_name)->subject('Order #'.$order->id.' confirmation');
    });
}

上面的更改已在生产站点上进行了一周以上,并且自更改以来,没有一个单独的问题实例。

我唯一可以得出的结论是Laravel事件系统中的某种错误,以某种方式破坏了传递的对象。还是有其他事情在起作用?

更新3
我似乎还为时过早,指出将代码移到事件之外可以解决该问题-实际上,通过记录,在过去的两天内,我可以看到发出了更多不正确的订单确认信息(在将近3个订单后,总共发送了5个)周没有问题)。

我注意到已收到恶意订单确认的用户ID似乎正在递增(不是没有空缺,而是仍然按升序排列)。

我还注意到,每个问题订单都是通过现金和帐户信用付款的-大多数都是现金付款。我对此进行了进一步研究,用户ID实际上是相关信用交易的ID!

以上是尝试解决此问题的第一个铸铁突破。仔细检查后,我发现问题仍然是随机的-有相当多(至少50%)的订单已通过帐户信用支付给没有用户帐户的客户,但并未导致发送错误的电子邮件(尽管关联的信用交易ID与用户ID匹配)。

因此,问题仍然是随机的,或者似乎是随机的。我的积分兑换事件是这样触发的:
Event::fire(new CreditRedemption( $credit, $order ));

上面的代码恰好在我的OrderSuccess事件之前被调用-如您所见,这两个事件都传递了$order模型对象。

我的CreditRedemption事件处理程序如下所示:
public function handle(CreditRedemption $event)
{
    // make sure redemption amount is a negative value
    if($event->credit < 0) {
        $amount = $event->credit;
    }
    else {
        $amount = ($event->credit * -1);
    }

    // create the credit transaction
    $credit_transaction = New Credit_transaction();
    $credit_transaction->transaction_type = 'Credit Redemption';
    $credit_transaction->amount = $amount; // negative value
    $credit_transaction->customer_id = $event->order->customer->id;
    $credit_transaction->order_id = $event->order->id;

    // record staff member if appropriate
    if(!is_null($event->order->staff)) {
        $credit_transaction->staff_id = $event->order->staff->id;
    }

    // save transaction
    $credit_transaction->save();

    return $credit_transaction;
}
$credit_transaction->save();正在我的credit_transactions表中生成ID,Laravel会使用该ID来检索用户对象。从上面的处理程序中可以看出,我在任何时候都不会更新$order对象。

Laravel如何使用(还记得,仍然是随机的,大约少于50%的时间)使用我新创建的$credit_transaciton的ID来填充$order->customer->user模型对象?

最佳答案

我不能帮助您找到导致问题的根源,但是我可以根据问题的更新1中提供的逻辑来提供可能的解决方法。

原始逻辑

$customer = Customer::find($order->customer_id);
$user = User::find($customer->user_id);

if(!is_null($user)) {
    // send email and log actions etc
}

修改后的逻辑

由于客户user_id可以为null,因此将返回的客户限制为具有user_id的客户可能更有效。这可以通过使用whereNotNull()方法来实现。然后,我们可以继续检查是否有回头客,如果有,请发送电子邮件等。
$customer = Customer::whereNotNull('user_id')->find($order->customer_id); 

if (!$customer->isEmpty()) { 
    // send email and log actions etc 
}

通过使应用程序没有机会返回带有空user_id的客户,这有望解决您的问题,但是不幸的是,它并没有说明其最初发生的原因。

关于php - Laravel 5.1 Eloquent ORM随机返回不正确的关系-*重大更新*,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33607125/

相关文章:

javascript - 如何在 laravel 的 onclick 函数上传递值

php - 向复杂的 SQL select 语句添加字段

php - 如何在外部文件中使用 Symfony 的默认自动加载

php - 'xmlParseEntityRef : no name' warnings while loading xml into a php file

php - 如何在 MySQL 和 PHP 中使用嵌套集获取结构化结果?

mysql - ErrorException (E_ERROR) rawurlencode() 期望参数 1 为字符串,给定对象

php - 将 Laravel 5 Auth 与自定义表字段名称一起使用?

php - laravel(lumen) 查询中的 Postgis ST_DWithin 函数

css - 如何在 Assets 管道插件中使用字体和背景图像?

php - WHERE 子句与 Laravel 中另一个表的关系