Skip to content

Instantly share code, notes, and snippets.

@juampi92
Last active December 9, 2020 15:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save juampi92/567a944e24b8ea51ac0badc0bb1a1b75 to your computer and use it in GitHub Desktop.
Save juampi92/567a944e24b8ea51ac0badc0bb1a1b75 to your computer and use it in GitHub Desktop.
Database master-slave replication delay in Laravel
<?php
/**
* Will check if the write host is the same as the
* read host given a connection name.
*
* @param string $connectionName
* @return bool
*/
function connectionHasSameReadAndWrite(string $connectionName): bool
{
return config("database.connections.$connectionName.write.host")
=== config("database.connections.$connectionName.read.host");
}
<?php
namespace App\Providers;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\ServiceProvider;
class SafeQueryServiceProvider extends ServiceProvider
{
public function boot()
{
$safeCall = function ($method, $arguments) {
$query = $this->getQuery();
// First we try to get the results of the untouched method.
$result = $this->$method(...$arguments);
$isCollectionResult = $result instanceof \Illuminate\Support\Collection;
// If we are already using the write connection,
// or we got a non-empty result, we return it.
if ($query->useWritePdo
// If it's a collection, we check that it's not empty
|| ($isCollectionResult && $result->isNotEmpty())
// If it's not a collection, it's a model, so we check it's not null
|| (!$isCollectionResult && !is_null($result))
) {
return $result;
}
$connectionName = $query
->getConnection()
->getConfig('name');
// If the write connection is equal to the read connection, there's
// no need to try again on the write PDO, so we return the empty result.
if (connectionHasSameReadAndWrite($connectionName)) {
return $result;
}
// We try the same call, but now on the write connection.
return $this
->useWritePdo()
->$method(...$arguments);
};
Builder::macro('safeGet', function (...$arguments) use ($safeCall) {
return $safeCall->call($this, 'get', $arguments);
});
Builder::macro('safeFind', function (...$arguments) use ($safeCall) {
return $safeCall->call($this, 'find', $arguments);
});
Builder::macro('safeFirst', function (...$arguments) use ($safeCall) {
return $safeCall->call($this, 'first', $arguments);
});
Builder::macro('safePluck', function (...$arguments) use ($safeCall) {
return $safeCall->call($this, 'pluck', $arguments);
});
Builder::macro('safeFindOrFail', function (...$arguments) {
$query = $this->getQuery();
// Instead of analyzing the output, we just catch the exception
try {
return $this->findOrFail(...$arguments);
} catch (ModelNotFoundException $e) {
$connectionName = $query
->getConnection()
->getConfig('name');
// If we are already using the write connection,
// or if the write connection is equal to the write
// connection, there's no need to try again on the write PDO,
// so we will throw again the same ModelNotFound exception.
if ($query->useWritePdo || !connectionHasSameReadAndWrite($connectionName)) {
throw $e;
}
// We try the same call, but now on the write connection.
return $this
->useWritePdo()
->findOrFail(...$arguments);
}
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment