mikebronner / laravel-model-caching

Eloquent model-caching made easy.
MIT License
2.26k stars 215 forks source link

Object of class Illuminate\\Database\\Query\\Expression could not be converted to string #441

Closed vheins closed 1 year ago

vheins commented 1 year ago

Describe the bug A clear and concise description of what the bug is.

Eloquent Query Please provide the complete eloquent query that caused the bug, for example:

$data->whereHas($relation, function ($q) use ($search, $searchKey) {
    $table = $q->getModel()->getTable();
    $q->whereRaw("LOWER(cast(" . $table . "." . $searchKey . " as text)) ilike '%" . $search . "%'");
});

Stack Trace

{
    "success": false,
    "code": 500,
    "status": "Internal Server Error",
    "message": {
        "error": "Object of class Illuminate\\Database\\Query\\Expression could not be converted to string",
        "trace": [
            {
                "file": "/var/www/html/vendor/genealabs/laravel-model-caching/src/CacheKey.php",
                "line": 346,
                "function": "getColumnClauses",
                "class": "GeneaLabs\\LaravelModelCaching\\CacheKey",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Collections/Traits/EnumeratesValues.php",
                "line": 734,
                "function": "GeneaLabs\\LaravelModelCaching\\{closure}",
                "class": "GeneaLabs\\LaravelModelCaching\\CacheKey",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/genealabs/laravel-model-caching/src/CacheKey.php",
                "line": 352,
                "function": "reduce",
                "class": "Illuminate\\Support\\Collection",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/genealabs/laravel-model-caching/src/CacheKey.php",
                "line": 154,
                "function": "getWhereClauses",
                "class": "GeneaLabs\\LaravelModelCaching\\CacheKey",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/genealabs/laravel-model-caching/src/CacheKey.php",
                "line": 345,
                "function": "getNestedClauses",
                "class": "GeneaLabs\\LaravelModelCaching\\CacheKey",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Collections/Traits/EnumeratesValues.php",
                "line": 734,
                "function": "GeneaLabs\\LaravelModelCaching\\{closure}",
                "class": "GeneaLabs\\LaravelModelCaching\\CacheKey",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/genealabs/laravel-model-caching/src/CacheKey.php",
                "line": 352,
                "function": "reduce",
                "class": "Illuminate\\Support\\Collection",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/genealabs/laravel-model-caching/src/CacheKey.php",
                "line": 51,
                "function": "getWhereClauses",
                "class": "GeneaLabs\\LaravelModelCaching\\CacheKey",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/genealabs/laravel-model-caching/src/Traits/Caching.php",
                "line": 182,
                "function": "make",
                "class": "GeneaLabs\\LaravelModelCaching\\CacheKey",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/genealabs/laravel-model-caching/src/Traits/Buildable.php",
                "line": 173,
                "function": "makeCacheKey",
                "class": "GeneaLabs\\LaravelModelCaching\\CachedBuilder",
                "type": "->"
            },
            {
                "file": "/var/www/html/app/Http/Controllers/Controller.php",
                "line": 69,
                "function": "paginate",
                "class": "GeneaLabs\\LaravelModelCaching\\CachedBuilder",
                "type": "->"
            },
            {
                "file": "/var/www/html/modules/Subscription/Controllers/SubscriptionController.php",
                "line": 28,
                "function": "search",
                "class": "App\\Http\\Controllers\\Controller",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Controller.php",
                "line": 54,
                "function": "index",
                "class": "IDS\\Subscription\\Controllers\\SubscriptionController",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php",
                "line": 43,
                "function": "callAction",
                "class": "Illuminate\\Routing\\Controller",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Route.php",
                "line": 260,
                "function": "dispatch",
                "class": "Illuminate\\Routing\\ControllerDispatcher",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Route.php",
                "line": 205,
                "function": "runController",
                "class": "Illuminate\\Routing\\Route",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
                "line": 798,
                "function": "run",
                "class": "Illuminate\\Routing\\Route",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 141,
                "function": "Illuminate\\Routing\\{closure}",
                "class": "Illuminate\\Routing\\Router",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/spatie/laravel-permission/src/Middlewares/PermissionMiddleware.php",
                "line": 24,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "Spatie\\Permission\\Middlewares\\PermissionMiddleware",
                "type": "->"
            },
            {
                "file": "/var/www/html/app/Http/Middleware/JWTMiddleware.php",
                "line": 50,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "App\\Http\\Middleware\\JWTMiddleware",
                "type": "->"
            },
            {
                "file": "/var/www/html/app/Http/Middleware/ConvertResponseToCamelCase.php",
                "line": 21,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "App\\Http\\Middleware\\ConvertResponseToCamelCase",
                "type": "->"
            },
            {
                "file": "/var/www/html/app/Http/Middleware/ConvertRequestToSnakeCase.php",
                "line": 27,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "App\\Http\\Middleware\\ConvertRequestToSnakeCase",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php",
                "line": 50,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "Illuminate\\Routing\\Middleware\\SubstituteBindings",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php",
                "line": 126,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php",
                "line": 102,
                "function": "handleRequest",
                "class": "Illuminate\\Routing\\Middleware\\ThrottleRequests",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php",
                "line": 54,
                "function": "handleRequestUsingNamedLimiter",
                "class": "Illuminate\\Routing\\Middleware\\ThrottleRequests",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "Illuminate\\Routing\\Middleware\\ThrottleRequests",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 116,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
                "line": 799,
                "function": "then",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
                "line": 776,
                "function": "runRouteWithinStack",
                "class": "Illuminate\\Routing\\Router",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
                "line": 740,
                "function": "runRoute",
                "class": "Illuminate\\Routing\\Router",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
                "line": 729,
                "function": "dispatchToRoute",
                "class": "Illuminate\\Routing\\Router",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
                "line": 200,
                "function": "dispatch",
                "class": "Illuminate\\Routing\\Router",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 141,
                "function": "Illuminate\\Foundation\\Http\\{closure}",
                "class": "Illuminate\\Foundation\\Http\\Kernel",
                "type": "->"
            },
            {
                "file": "/var/www/html/app/Http/Middleware/DBTransaction.php",
                "line": 28,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "App\\Http\\Middleware\\DBTransaction",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
                "line": 21,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php",
                "line": 31,
                "function": "handle",
                "class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
                "line": 21,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php",
                "line": 40,
                "function": "handle",
                "class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "Illuminate\\Foundation\\Http\\Middleware\\TrimStrings",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php",
                "line": 27,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php",
                "line": 86,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php",
                "line": 62,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "Illuminate\\Http\\Middleware\\HandleCors",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php",
                "line": 39,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "Illuminate\\Http\\Middleware\\TrustProxies",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/ClockworkMiddleware.php",
                "line": 24,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 180,
                "function": "handle",
                "class": "Clockwork\\Support\\Laravel\\ClockworkMiddleware",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
                "line": 116,
                "function": "Illuminate\\Pipeline\\{closure}",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
                "line": 175,
                "function": "then",
                "class": "Illuminate\\Pipeline\\Pipeline",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
                "line": 144,
                "function": "sendRequestThroughRouter",
                "class": "Illuminate\\Foundation\\Http\\Kernel",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/octane/src/ApplicationGateway.php",
                "line": 37,
                "function": "handle",
                "class": "Illuminate\\Foundation\\Http\\Kernel",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/octane/src/Worker.php",
                "line": 92,
                "function": "handle",
                "class": "Laravel\\Octane\\ApplicationGateway",
                "type": "->"
            },
            {
                "file": "/var/www/html/vendor/laravel/octane/bin/swoole-server",
                "line": 124,
                "function": "handle",
                "class": "Laravel\\Octane\\Worker",
                "type": "->"
            },
            {
                "function": "{closure}"
            },
            {
                "file": "/var/www/html/vendor/laravel/octane/bin/swoole-server",
                "line": 170,
                "function": "start",
                "class": "Swoole\\Server",
                "type": "->"
            }
        ],
        "code": 0,
        "status": 500
    }
}

Environment

Additional context Add any other context about the problem here.

mikebronner commented 1 year ago

Can you provide a test repository that recreates this issue, or write a failing test in a PR that recreates this issue?

vheins commented 1 year ago

This is my controller,

        if ($request->input('search_type')) $this->search_type = Str::snake($request->search_type);
        if ($request->input('per_page')) $this->per_page = $request->per_page;
        if ($request->input('sort_by')) $this->sort_by = Str::snake($request->sort_by);
        if ($request->input('sort_key')) $this->sort_key = ($request->sort_key == 'asc') ? 'asc' : 'desc';
        if ($request->input('search')) {
            $search = Str::lower(str_replace(' ', '%', $request->input('search')));
            if (Str::contains($this->search_type, '.')) {
                $keys = explode('.', $this->search_type);
                foreach ($keys as $value) {
                    $key[] = Str::camel($value);
                }
                $searchKey = end($key);
                array_pop($key);
                $relation = implode('.', $key);
                $data->whereHas($relation, function ($q) use ($search, $searchKey) {
                    $table = $q->getModel()->getTable();
                    $q->where($table . '.' . Str::snake($searchKey), 'like', '%' . $search . '%');
                });
            } else {
                $searchType = Str::of($this->search_type)->camel()->toString();
                if (isset($this->mapRelation[$searchType])) {
                    $search_type = $this->mapRelation[$searchType];
                } else {
                    $search_type = $this->search_type;
                }
                $case = is_numeric($search) ? $search_type : "LOWER(" . $search_type . ")";
                $data->whereRaw($case . " like '%" . $search . "%'");
            }
        }

its work before on laravel 9.53 an this version 0.12.5

but when i try to upgrade to Laravel 10 & need upgrade laravel-model-caching to latest version, its throw an error Object of class Illuminate\Database\Query\Expression could not be converted to string so i'm revert back to laravel 9.53 and 0.125

vheins commented 1 year ago

its same to

$data->whereHas('user', function ($q) use ($search, $searchKey) {
    $table = $q->getModel()->getTable();
    $q->where('users.name', 'like', '%boo yaaa%');
});

or

$data->whereHas('other.relation', function ($q) use ($search, $searchKey) {
    $table = $q->getModel()->getTable();
    $q->where('relation.field, 'like', '%boo yaaa%');
});
vheins commented 1 year ago

i Found the issue , its on getColumnClauses

    protected function getColumnClauses(array $where) : string
    {
        if ($where["type"] !== "Column") {
            return "";
        }

        return "-{$where["boolean"]}_{$where["first"]}_{$where["operator"]}_{$where["second"]}";
    }

i try to dump $where variable and got

array:5 [ // vendor/genealabs/laravel-model-caching/src/CacheKey.php:85
  "type" => "Column"
  "first" => Illuminate\Database\Query\Expression {#2342
    #value: ""subscriptions"."subscriber_id""
  }
  "operator" => "="
  "second" => "users.id"
  "boolean" => "and"
]

in my case $where["first"] return Illuminate\Database\Query\Expression refer to https://laravel.com/docs/10.x/upgrade#database-expressions however, if your application is manually casting database expressions to strings using (string) or invoking the __toString method on the expression directly, you should update your code to invoke the getValue method instead:

so i try overide that variable into

$where["first"] = $where["first"]->getValue(\DB::connection()->getQueryGrammar());

and its works

may i send pull request @mikebronner ?

vheins commented 1 year ago

i had send PR on #442 Thanks you

vheins commented 1 year ago

Fix at #442