如何在laravel 5中为失败的作业插入覆盖数据库连接


How to override database connection for failed job insertion in laravel 5?

我正在尝试开发一个多租户多数据库应用程序,这基本上意味着每个租户都有自己的数据库、用户、资源等。

当然,当收到请求时,Laravel需要知道要使用哪个数据库连接,所以我编写了一个中间件,它基本上解析请求中的JWT,查找租户id或用户名,然后简单地连接到租户的数据库。

但现在我正在处理队列,我正试图超越laravel 5的默认行为,它连接到主数据库并插入失败的作业记录。

当我深入查看供应商文件时,我发现了一个FailedJobProvider接口:

<?php
namespace Illuminate'Queue'Failed;
interface FailedJobProviderInterface
{
    /**
     * Log a failed job into storage.
     *
     * @param  string  $connection
     * @param  string  $queue
     * @param  string  $payload
     * @return void
     */
    public function log($connection, $queue, $payload);
    /**
     * Get a list of all of the failed jobs.
     *
     * @return array
     */
    public function all();
    /**
     * Get a single failed job.
     *
     * @param  mixed  $id
     * @return array
     */
    public function find($id);
    /**
     * Delete a single failed job from storage.
     *
     * @param  mixed  $id
     * @return bool
     */
    public function forget($id);
    /**
     * Flush all of the failed jobs from storage.
     *
     * @return void
     */
    public function flush();
}

以及实现该接口的DatabaseFailedJobProvider类:

<?php
namespace Illuminate'Queue'Failed;
use Carbon'Carbon;
use Illuminate'Database'ConnectionResolverInterface;
class DatabaseFailedJobProvider implements FailedJobProviderInterface
{
    /**
     * The connection resolver implementation.
     *
     * @var 'Illuminate'Database'ConnectionResolverInterface
     */
    protected $resolver;
    /**
     * The database connection name.
     *
     * @var string
     */
    protected $database;
    /**
     * The database table.
     *
     * @var string
     */
    protected $table;
    /**
     * Create a new database failed job provider.
     *
     * @param  'Illuminate'Database'ConnectionResolverInterface  $resolver
     * @param  string  $database
     * @param  string  $table
     * @return void
     */
    public function __construct(ConnectionResolverInterface $resolver, $database, $table)
    {
        $this->table = $table;
        $this->resolver = $resolver;
        $this->database = $database;
    }
    /**
     * Log a failed job into storage.
     *
     * @param  string  $connection
     * @param  string  $queue
     * @param  string  $payload
     * @return void
     */
    public function log($connection, $queue, $payload)
    {
        $failed_at = Carbon::now();
        $this->getTable()->insert(compact('connection', 'queue', 'payload', 'failed_at'));
    }
    /**
     * Get a list of all of the failed jobs.
     *
     * @return array
     */
    public function all()
    {
        return $this->getTable()->orderBy('id', 'desc')->get();
    }
    /**
     * Get a single failed job.
     *
     * @param  mixed  $id
     * @return array
     */
    public function find($id)
    {
        return $this->getTable()->find($id);
    }
    /**
     * Delete a single failed job from storage.
     *
     * @param  mixed  $id
     * @return bool
     */
    public function forget($id)
    {
        return $this->getTable()->where('id', $id)->delete() > 0;
    }
    /**
     * Flush all of the failed jobs from storage.
     *
     * @return void
     */
    public function flush()
    {
        $this->getTable()->delete();
    }
    /**
     * Get a new query builder instance for the table.
     *
     * @return 'Illuminate'Database'Query'Builder
     */
    protected function getTable()
    {
        return $this->resolver->connection($this->database)->table($this->table);
    }
}

所以我想,如果我编写自己的提供者,或者可以超越这个提供者,我就可以在插入失败的作业之前告诉laravel要连接到哪个数据库。但我对SOLID或OOP不太熟悉,在这种情况下我会感到困惑。

如何编写我自己的提供者或超越这个提供者来在旅途中更改数据库连接?

我知道这太晚了,但我遇到了同样的问题。我想明白了。因此,对于其他遇到这个问题的人来说,这就是如何做到的:

首先,您需要创建自己的失败作业提供程序类来实现FailedJobProviderInterface接口。我建议将代码从Illuminate'Queue'Failed'DatabaseFailedJobProvider复制到您的自定义类中,并简单地更改它以使其按您需要的方式工作。Laravel使用该类中的其余函数来完成多项任务,并且该类本身需要与实现的接口相匹配。

我只是简单地更改了日志方法,将额外的数据记录到数据库中的额外列中。

完成后,在服务提供商中,无论是您自己的还是默认的,您都需要包含刚刚创建的新的失败作业提供商类。

然后在服务提供商的引导方法中放入以下代码:

// Get a default implementation to trigger a deferred binding
$_ = $this->app['queue.failer'];
//regiter the custom class you created
$this->app->singleton('queue.failer', function ($app) {
    $config = $app['config']['queue.failed'];
    return new NAMEOFYOURCLASS($app['db'], $config['database'], $config['table']);
});

该代码覆盖了Laravel完成的注册。

为了代码的清晰性,您可以将该代码作为一个函数放在服务提供程序中,并在引导方法中运行该方法。

如果您想将不同的数据记录到数据库中,请确保同时更新失败的作业迁移和/或数据库表。

现在,当一个作业失败时,您的自定义代码将按照您的意愿运行日志记录失败的作业,即使您更新了Laravel版本,也应该可以正常工作。

我更晚参加聚会,但在研究这个主题时发现了这篇帖子,所以我想我也会给出自己的解决方案。

我发现处理这个问题最干净的方法是使用(取消)序列化。

我总是把SerializesModels特征分配给我的工作,我用这种方式"扩展"了它(为了清晰起见,简化了):

<?php
namespace App'Jobs'Traits;
use Illuminate'Queue'SerializesModels;
trait RestoresTenant
{
    use SerializesModels {
        SerializesModels::__serialize as serialize;
        SerializesModels::__unserialize as unserialize;
    }
    /**
     * The tenant ID.
     *
     * @var string
     */
    protected $tenantId;
    /**
     * Save the current tenant before serialization.
     *
     * @return array
     */
    public function __serialize()
    {
        // SET THE CURRENT TENANT ID HERE, OR WHATEVER YOU USE TO IDENTIFY IT
        // $this->tenantId = ...
        return $this->serialize();
    }
    /**
     * Restore the tenant upon unserialization.
     *
     * @param  array $values
     * @return array
     */
    public function __unserialize(array $values)
    {
        // RETRIEVE THE TENANT ID AND RESTORE THE DATABASE CONNECTION HERE
        // $tenantId = $values["'0*'0tenantId"];
        // ...
        return $this->unserialize($values);
    }
}

我的作业使用RestoresTenant特性,这意味着租户数据库连接在作业的其余属性未序列化之前,甚至在执行作业中间件之前就已恢复。

基本上,除非在非序列化开始之前出现问题,否则失败的作业将记录在正确租户的数据库中。