У меня есть приложение 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
недействительным и, если true, отправляется подтверждение заказа. Если он является нулевым (что часто бывает), то подтверждение не отправляется.
Как видно из вышеизложенного, я добавил журнал для записи объектов при отправке письма. Вот пример ошибочного (опять-таки, усеченного для краткости):
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] => [email protected]
[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] => [email protected]
[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, если она кажется некоторой формой ошибки.
Обновление. Относительно некоторых ответов/комментариев, которые были предложены, я попытался удалить любые потенциальные проблемы ОРВ, чтобы получить данные вручную, например:
$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. Чтобы попробовать другое решение, я извлек код события из обработчика событий и поместил его в свой контроллер - раньше я запускал событие OrderSuccess с помощью Event::fire(new OrderSuccess ($order));
, и вместо этого я прокомментировал эту строку и просто поместил код обработчика события в метод контроллера:
$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 Кажется, я преждевременно заявлял, что мой код за пределами события исправил проблему - на самом деле, через мой журнал, за последние 2 дня я увидел, что было отправлено еще некорректное подтверждение заказа (всего 5, после почти 3 недель без проблем).
Я заметил, что идентификаторы пользователей, получившие подтверждения заказа изгоев, по-видимому, увеличиваются (не без пробелов, но все еще в порядке возрастания).
Я также заметил, что каждый из проблемных заказов был оплачен наличными и кредитными картами - большинство из них только наличные. Я смотрел дальше на это, и идентификаторы пользователей на самом деле являются идентификаторами связанных кредитных транзакций!
Вышеупомянутый - это первый прорыв чугуна в попытке решить эту проблему. При ближайшем рассмотрении я вижу, что проблема по-прежнему произвольна - существует довольно много (по меньшей мере 50%) заказов, которые были оплачены с помощью учетной записи клиента для пользователя без учетной записи пользователя, но это не вызвало неправильных отправлений электронной почты (несмотря на то, что связанный идентификатор кредитной транзакции совпадает с идентификатором пользователя).
Таким образом, проблема все еще случайна или, по-видимому, так. Событие погашения кредита запускается следующим образом:
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();
генерирует id в моей таблице credit_transactions
, которая каким-то образом используется Laravel для извлечения пользовательского объекта. Как видно из вышеописанного обработчика, я не обновляю свой объект $order
в любой точке.
Как использует Laravel (помните, все еще случайно, около 50% времени) идентификатор моего вновь созданного $credit_transaciton
, чтобы заполнить объект модели $order->customer->user
?