我试图缓存我的模型,以避免重复的数据库查找。我能够轻松地覆盖find
方法,如下所示:
<?php
namespace App\Concerns;
use Illuminate\Support\Facades\Cache;
trait CachesModels
{
protected function findInCache(...$args)
{
$id = $args[0];
if (is_numeric($id)) {
$key = self::class . "|" . $id;
$timeout = config("app.cache_seconds");
return Cache::remember($key, $timeout, function() use ($args) {
return $this->forwardCallTo($this->newQuery(), "find", $args);
});
}
return $this->forwardCallTo($this->newQuery(), "find", $args);
}
public static function find(...$args)
{
return (new static)->findInCache($args);
}
}
通过在我的模型中使用这个特性,我能够缓存find()
方法的结果。但是,这并没有考虑到其他相关方法(例如findOrFail
),更重要的是,没有使用缓存对我的控制器方法进行依赖注入。
在浏览了中间件和跟踪代码之后,在所有这些情况下,我似乎可以通过重写where()
方法来使用缓存。与find()
一样,这不是父类上定义的方法,也可以静态地或在实例上调用。因此,我尝试了以下代码:
<?php
namespace App\Concerns;
use Illuminate\Support\Facades\Cache;
trait CachesModels
{
protected function whereInCache($parameters)
{
// only cache simple calls, e.g. where('id', 123)
if (count($parameters) === 2 && is_numeric($parameters[1]) && $parameters[0] === $this->getKeyName()) {
$key = self::class . "|" . $parameters[1];
$timeout = config("app.cache_seconds");
return Cache::remember($key, $timeout, function() use ($parameters) {
return parent::where(...$parameters);
});
}
return parent::where(...$parameters);
}
public function __call($method, $parameters)
{
if ($method === "where") {
return $this->whereInCache($parameters);
}
return parent::__call($method, $parameters);
}
public static function __callStatic($method, $parameters)
{
if ($method === "where") {
return (new static)->whereInCache($parameters);
}
return parent::__callStatic($method, $parameters);
}
}
但是,当我尝试运行这段代码时,会得到一个递归错误:
PHP Error: Maximum function nesting level of '256' reached, aborting!
in vendor/laravel/framework/src/Illuminate/Container/Container.php on line 1161
我还尝试将return parent::where(...$parameters);
替换为在Model::__call()
方法中使用的return $this->forwardCallTo($this->newQuery(), "where", $parameters);
,但这给出了一个不同的错误:
Exception with message 'Serialization of 'Closure' is not allowed'
在缓存闭包返回行上。不确定这个错误,因为方法应该返回Illuminate\Database\Eloquent\Builder
的一个实例。
对我哪里出错有什么想法吗?
发布于 2020-10-08 18:57:20
最后,我能够用以下简单代码复制递归错误:
<?php
class A {
public static function foo() {
echo "3";
}
}
class B {
public function __call($m, $p) {
echo "2";
A::foo();
}
}
class C extends B {
public function foo() {
echo "1";
parent::foo();
}
public function __call($m, $p){
echo "x";
$this->foo();
}
}
(new C)->foo();
// output: 1x1x1x1x1x1x...
这个输出是出乎意料的--在B
上调用不可访问的方法似乎是在触发C::__call()
而不是B::__call()
。将对parent::foo()
的调用替换为parent::__call("foo", null)
将解决此错误,并给出123
的预期输出。一个极好的提醒为什么“魔术”方法是一场噩梦。
解决了这个问题后,我又回到了Serialization of 'Closure' is not allowed
。正如问题中提到的,函数应该返回Illuminate\Database\Eloquent\Builder
的一个实例,但是这个对象确实包含一个闭包,没有办法解决这个问题。
我的解决方案是覆盖Illuminate\Database\Eloquent\Model::resolveRouteBinding()
以及find
和findOrFail
方法。它仍然不理想,但涵盖了我多次访问数据库的大多数情况。如果模型被更新,一个简单的事件侦听器刷新缓存条目:
<?php
namespace App\Concerns;
use Illuminate\Support\Facades\Cache;
trait CachesModels
{
public static function bootCachesModels()
{
static::updated(function($model) {
$key = $model->getCacheKey();
$timeout = $model->getCacheTimeout();
Cache::forget($key);
Cache::put($key, $model, $timeout);
});
static::deleted(function($model) {
$key = $model->getCacheKey();
Cache::forget($key);
});
}
private function getCacheKey(int $id = null)
{
return self::class . "|" . ($id ?? $this->getKey());
}
private function getCacheTimeout()
{
return config("app.cache_seconds");
}
private function findInCache($args)
{
$id = $args[0];
if (is_numeric($id)) {
$key = $this->getCacheKey($id);
$timeout = $this->getCacheTimeout();
return Cache::remember($key, $timeout, function() use ($args) {
return $this->forwardCallTo($this->newQuery(), "find", $args);
});
}
return $this->forwardCallTo($this->newQuery(), "find", $args);
}
private function findOrFailInCache($args)
{
$id = $args[0];
if (is_numeric($id)) {
$key = $this->getCacheKey($id);
$timeout = $this->getCacheTimeout();
return Cache::remember($key, $timeout, function() use ($args) {
return $this->forwardCallTo($this->newQuery(), "findOrFail", $args);
});
}
return $this->forwardCallTo($this->newQuery(), "findOrFail", $args);
}
public static function find(...$args)
{
return (new static)->findInCache($args);
}
public static function findOrFail(...$args)
{
return (new static)->findOrFailInCache($args);
}
public function resolveRouteBinding($value, $field = null)
{
// only cache when route binding is based on primary key
if ($field !== null && $field !== $this->getKeyName()) {
return parent::resolveRouteBinding($value, $field);
}
$key = $this->getCacheKey($value);
$timeout = $this->getCacheTimeout();
return Cache::remember($key, $timeout, function() use($value, $field) {
return parent::resolveRouteBinding($value, $field);
});
}
}
发布于 2020-10-07 22:24:18
第一个问题是无限递归(某些东西一次又一次地调用自己,直到它在256个递归级别之后出现错误);第二个问题基于匿名函数(又名闭包)不能序列化(至少在默认情况下不能序列化,以防止代码注入)。
很可能是Laravel框架正在调用一个方法,而不一定是您的代码。
例如:当侦听Model
在更新和尝试保存Model
在那里。在这种情况下,为了避免创建无限递归,必须使用DB
而不是Model
。
https://stackoverflow.com/questions/64252974
复制相似问题