Last active
December 9, 2020 15:31
-
-
Save juampi92/567a944e24b8ea51ac0badc0bb1a1b75 to your computer and use it in GitHub Desktop.
Database master-slave replication delay in Laravel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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"); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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