首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >覆盖雄辩模型,其中()方法

覆盖雄辩模型,其中()方法
EN

Stack Overflow用户
提问于 2020-10-07 22:06:47
回答 2查看 314关注 0票数 1

我试图缓存我的模型,以避免重复的数据库查找。我能够轻松地覆盖find方法,如下所示:

代码语言:javascript
运行
复制
<?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()一样,这不是父类上定义的方法,也可以静态地或在实例上调用。因此,我尝试了以下代码:

代码语言:javascript
运行
复制
<?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);
    }
}

但是,当我尝试运行这段代码时,会得到一个递归错误:

代码语言:javascript
运行
复制
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);,但这给出了一个不同的错误:

代码语言:javascript
运行
复制
Exception with message 'Serialization of 'Closure' is not allowed'

在缓存闭包返回行上。不确定这个错误,因为方法应该返回Illuminate\Database\Eloquent\Builder的一个实例。

对我哪里出错有什么想法吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-10-08 18:57:20

最后,我能够用以下简单代码复制递归错误:

代码语言:javascript
运行
复制
<?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()以及findfindOrFail方法。它仍然不理想,但涵盖了我多次访问数据库的大多数情况。如果模型被更新,一个简单的事件侦听器刷新缓存条目:

代码语言:javascript
运行
复制
<?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);
        });
    }
}
票数 1
EN

Stack Overflow用户

发布于 2020-10-07 22:24:18

第一个问题是无限递归(某些东西一次又一次地调用自己,直到它在256个递归级别之后出现错误);第二个问题基于匿名函数(又名闭包)不能序列化(至少在默认情况下不能序列化,以防止代码注入)。

很可能是Laravel框架正在调用一个方法,而不一定是您的代码。

例如:当侦听Model在更新和尝试保存Model在那里。在这种情况下,为了避免创建无限递归,必须使用DB而不是Model

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64252974

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档