我有一个Laravel应用程序,用于支持一个中等流量的电子商务网站。该网站允许人们通过前端下订单,但它还具备后端功能,可以通过呼叫中心接受电话订单。
订单与客户相关联,客户可以选择成为用户 - 用户是拥有前端登录帐户的人。没有用户帐户的客户仅会在通过呼叫中心接受订单时创建。
我遇到的问题非常奇怪,我认为可能是某种Laravel bug。
这种情况只会偶尔发生,但出现的问题是当通过呼叫中心接受无用户帐户的客户订单时,一份订单确认会被发送给一个随机的用户 - 很明显是随意从数据库中选择的用户,尽管数据中没有关联。
这些是项目模型的相关部分:
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
对象是否为空,如果为真,则发送订单确认。如果它为空(它经常为空),则不发送确认。
从上面可以看出,我添加了一个记录对象的日志,当一封邮件被发送时。下面是一个错误示例(为简洁起见再次截断):
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 的问题,还是系统中的其他小怪物?
请提供任何想法 - 如果它似乎是某种错误,我可能会在 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 由于第一个更新没有任何帮助,我回到使用原始的Eloquent方法。为了尝试另一种解决方案,我将我的事件代码从事件处理程序中拿出来,并将其放置在我的控制器中——我之前使用Event::fire(new OrderSuccess ($order));
来触发OrderSuccess事件,现在我将这行注释并将事件处理程序代码放在控制器方法中:
$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:似乎我过早地宣布将代码移动到事件之外解决了这个问题 - 实际上,通过我的日志,在过去的两天中,我可以看到又发送了一些不正确的订单确认(总共5个,在近3周没有问题之后)。
我注意到收到错误订单确认的用户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_transaction
的ID来填充$order->customer->user
模型对象?
user_id
= null时,会运行类似于此的查询:SELECT * FROM user WHERE user.id IS NULL
尽管我真的不知道这将如何返回一个具有id!= NULL的用户,因为您的日志显示。除非有一些奇怪的情况,您的数据库刚刚创建了一个用户,但在查询运行时还没有创建自动增量id,然后在返回数据时生成了id。 - Arvidvendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
,getRelationshipFromMethod
函数是用于获取关系的函数,以及vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php
,addConstraints
函数是用于设置关系约束的函数,即user.id IS NULL
。 - Arvid