You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel 10报错:调用未定义方法Staudenmeir\LaravelCte\Query\Builder::()

Laravel 10:调用未定义方法 Staudenmeir\LaravelCte\Query\Builder::() 问题排查

我在开发Laravel 10项目时,编写了一个检查用户Perk资格的服务类,构建查询时遇到错误:

Call to undefined method Staudenmeir\LaravelCte\Query\Builder::()

已确认checkEligibilityForUser返回的是Illuminate\Database\Eloquent\Builder实例,但不清楚问题原因。以下是服务类代码及调用方式:

服务类代码

<?php

namespace App\Services;

use App\Contracts\PerkEligibilityContract;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
use App\Models\Perk;
use App\Models\User;
use Carbon\Carbon;
use Throwable;

class PerkEligibilityService implements PerkEligibilityContract
{
    /**
     * The perk
     */
    private Perk $perk;

    /**
     * The user id for lookup
     */
    private int $userId;

    /**
     * The user ids for lookup
     */
    private array $userIds;

    /**
     * Validate and return operator
     */
    private function validateOperator(string $operator): string
    {
        return match ($operator) {
            '!=' => '!=',
            '>' => '>',
            '>=' => '>=',
            '<' => '<',
            '<=' => '<=',
            default => '='
        };
    }

    /**
     * Return the current date or date specified
     */
    private function currentDate(string $date = 'now'): Carbon
    {
        try {
            return Carbon::parse($date);
        } catch (Throwable $err) {
            return Carbon::now();
        }
    }

    /**
     * Calculate relative date
     *
     * ">=" - refers to "In the last N days", so any date from them to now
     * "<=" - refers to "More than N days", so any date before a given date not up to now
     */
    private function relativeDate(string $value, string $operator = '>='): Carbon
    {
        [$amount, $unit] = explode(':', $value);

        $now = $this->currentDate();

        return match ($unit) {
            'second', 'seconds' => $operator === '>=' ? $now->subSeconds((int) $amount) : $now->addSeconds((int) $amount),
            'minute', 'minutes' => $operator === '>=' ? $now->subMinutes((int) $amount) : $now->addMinutes((int) $amount),
            'hour', 'hours' => $operator === '>=' ? $now->subHours((int) $amount) : $now->addHours((int) $amount),
            'day', 'days' => $operator === '>=' ? $now->subDays((int) $amount) : $now->addDays((int) $amount),
            'week', 'weeks' => $operator === '>=' ? $now->subWeeks((int) $amount) : $now->addWeeks((int) $amount),
            'month', 'months' => $operator === '>=' ? $now->subMonths((int) $amount) : $now->addMonths((int) $amount),
            'year', 'years' => $operator === '>=' ? $now->subYears((int) $amount) : $now->subYears((int) $amount),
            default => $now
        };
    }

    /**
     * Validate and return value
     */
    private function validateValue(string $dataType, string $operatorType, string $operator, mixed $value): mixed
    {
        return match ($dataType) {
            'number' => (int) $value,
            'date' => match ($operatorType) {
                'relative' => $this->relativeDate($value, $operator),
                default => $this->currentDate('now')
            },
            default => $value
        };
    }

    /**
     * Apply query condition
     */
    private function applyQueryCondition(object $rule, Builder $query, string $column = ''): void
    {
        if (! Schema::hasColumn($query->getModel()->getTable(), $column ?? '')) return;

        // modify the value based on data type and dynamic field
        $value = $this->validateValue(
            $rule->data_type, $rule->operator_type, $this->validateOperator($rule->operator), $rule->value
        );

        // no need to return, query is directly applied back on $query instance
        match ($rule->operator) {
            'like' => $query->where($column, 'like', $value),
            '%like%' => $query->where($column, 'like', "%$value%"),
            'like%' => $query->where($column, 'like', "$value%"),
            '%like' => $query->where($column, 'like', "%$value"),
            'is_empty' => $query->where($column, '=', ''),
            'not_empty' => $query->where($column, '!=', ''),
            'is_null' => $query->whereNull($column),
            'not_null' => $query->whereNotNull($column),
            default => $query->where($column, $this->validateOperator($rule->operator), $value)
        };
    }

    /**
     * Check the eligibility
     */
    private function checkEligibilityConditions(Builder $models): void
    {
        $this->perk->rules->each(function (object $rule) use ($models) {
            if (Str::contains($rule->attribute, '.')) {
                $collection = Str::of($rule->attribute)->explode('.');
                $relationship = $collection->slice(0, -1)->implode('.');
                $column = $collection->last();

                $models->whereHas($relationship, function (Builder $query) use ($rule, $column) {
                    $this->applyQueryCondition($rule, $query, $column);
                });
            } else {
                $this->applyQueryCondition($rule, $models, $rule->attribute);
            }
        });
    }

    /**
     * Check the eligibility for the user
     */
    private function checkEligibilityForUser(): bool
    {
        $user = User::query()->where('id', $this->userId);

        $this->checkEligibilityConditions($user);

        return $user->exists();
    }

    /**
     * Check the eligibility for the users
     */
    private function checkEligibilityForUsers(): int
    {
        $users = User::query()->whereIn('id', $this->userIds);

        $this->checkEligibilityConditions($users);

        return $users->count();
    }

    /**
     * Set's the perk
     */
    public function perk(Perk $perk): self
    {
        $this->perk = $perk->load(['rules' => fn ($query) => $query->enabled()]);

        return $this;
    }

    /**
     * Set's the user id
     */
    public function userId(int $userId): self
    {
        $this->userId = $userId;

        return $this;
    }

    /**
     * Set's the user ids
     */
    public function userIds(array $userIds): self
    {
        $this->userIds = $userIds;

        return $this;
    }

    /**
     * Get a count of users that are eligible
     */
    public function areEligible(): int
    {
        if ($this->perk->rules->isNotEmpty()) {
            return $this->checkEligibilityForUsers();
        }

        return count($this->userIds);
    }

    /**
     * Is the user eligible
     */
    public function isEligible(): bool
    {
        if ($this->perk->rules->isNotEmpty()) {
            return $this->checkEligibilityForUser();
        }

        return true;
    }
}

调用代码

$perk = Perk::find($request->input('id'));
$user = auth()->user();

PerkEligibility::perk($perk)->userId($user->id)->isEligible();

问题原因及修复方案

核心原因

错误提示中的::()说明代码尝试调用一个空方法名,大概率是applyQueryCondition方法里的match表达式存在解析问题:PHP的match语法在某些场景下(比如分支值异常、扩展类兼容问题)会生成无效的查询构造器调用,再加上项目使用了Staudenmeir\LaravelCte扩展替换了原生查询构造器,进一步触发了这个奇怪的错误。

具体修复步骤

  1. 替换match为if-else结构
    把applyQueryCondition中的match语句改为传统的条件判断,避免解析异常:

    // 替换原match代码
    if ($rule->operator === 'like') {
        $query->where($column, 'like', $value);
    } elseif ($rule->operator === '%like%') {
        $query->where($column, 'like', "%$value%");
    } elseif ($rule->operator === 'like%') {
        $query->where($column, 'like', "$value%");
    } elseif ($rule->operator === '%like') {
        $query->where($column, 'like', "%$value");
    } elseif ($rule->operator === 'is_empty') {
        $query->where($column, '=', '');
    } elseif ($rule->operator === 'not_empty') {
        $query->where($column, '!=', '');
    } elseif ($rule->operator === 'is_null') {
        $query->whereNull($column);
    } elseif ($rule->operator === 'not_null') {
        $query->whereNotNull($column);
    } else {
        $operator = $this->validateOperator($rule->operator);
        $query->where($column, $operator, $value);
    }
    
  2. 校验$rule->operator的合法性
    在applyQueryCondition开头添加日志,排查是否存在异常的operator值:

    Log::debug('Perk rule details', ['rule' => $rule]);
    

    确保所有Perk规则中的operator都是validateOperator方法中定义的合法值,避免空值或未定义值进入逻辑。

  3. 更新Laravel CTE扩展版本
    执行命令更新扩展,确保和Laravel 10兼容:

    composer update staudenmeir/laravel-cte
    

内容的提问来源于stack exchange,提问作者Ryan H

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 19:24:58