Issues (6)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/CacheableEloquent.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Rinvex\Cacheable;
6
7
use Closure;
8
use Illuminate\Database\Eloquent\Model;
9
use Illuminate\Database\Eloquent\Builder;
10
11
trait CacheableEloquent
12
{
13
    /**
14
     * Register an updated model event with the dispatcher.
15
     *
16
     * @param \Closure|string $callback
17
     *
18
     * @return void
19
     */
20
    abstract public static function updated($callback);
21
22
    /**
23
     * Register a created model event with the dispatcher.
24
     *
25
     * @param \Closure|string $callback
26
     *
27
     * @return void
28
     */
29
    abstract public static function created($callback);
30
31
    /**
32
     * Register a deleted model event with the dispatcher.
33
     *
34
     * @param \Closure|string $callback
35
     *
36
     * @return void
37
     */
38
    abstract public static function deleted($callback);
39
40
    /**
41
     * Boot the cacheable eloquent trait for a model.
42
     *
43
     * @return void
44
     */
45
    public static function bootCacheableEloquent(): void
46
    {
47
        static::updated(function (Model $cachedModel) {
48
            ! $cachedModel->isCacheClearEnabled() || $cachedModel::forgetCache();
49
        });
50
51
        static::created(function (Model $cachedModel) {
52
            ! $cachedModel->isCacheClearEnabled() || $cachedModel::forgetCache();
53
        });
54
55
        static::deleted(function (Model $cachedModel) {
56
            ! $cachedModel->isCacheClearEnabled() || $cachedModel::forgetCache();
57
        });
58
    }
59
60
    /**
61
     * Store the given cache key for the given model by mimicking cache tags.
62
     *
63
     * @param string $modelName
64
     * @param string $cacheKey
65
     *
66
     * @return void
67
     */
68
    protected static function storeCacheKey(string $modelName, string $cacheKey): void
69
    {
70
        $keysFile = storage_path('framework/cache/data/rinvex.cacheable.json');
71
        $cacheKeys = static::getCacheKeys($keysFile);
72
73
        if (! isset($cacheKeys[$modelName]) || ! in_array($cacheKey, $cacheKeys[$modelName])) {
74
            $cacheKeys[$modelName][] = $cacheKey;
75
            file_put_contents($keysFile, json_encode($cacheKeys));
76
        }
77
    }
78
79
    /**
80
     * Get cache keys from the given file.
81
     *
82
     * @param string $file
83
     *
84
     * @return array
85
     */
86
    protected static function getCacheKeys($file): array
87
    {
88
        if (! file_exists($file)) {
89
            $dir = dirname($file);
90
            is_dir($dir) || mkdir($dir);
91
            file_put_contents($file, null);
92
        }
93
94
        return json_decode(file_get_contents($file), true) ?: [];
95
    }
96
97
    /**
98
     * Flush cache keys of the given model by mimicking cache tags.
99
     *
100
     * @param string $modelName
101
     *
102
     * @return array
103
     */
104
    protected static function flushCacheKeys(string $modelName): array
105
    {
106
        $flushedKeys = [];
107
        $keysFile = storage_path('framework/cache/data/rinvex.cacheable.json');
108
        $cacheKeys = static::getCacheKeys($keysFile);
109
110
        if (isset($cacheKeys[$modelName])) {
111
            $flushedKeys = $cacheKeys[$modelName];
112
113
            unset($cacheKeys[$modelName]);
114
115
            file_put_contents($keysFile, json_encode($cacheKeys));
116
        }
117
118
        return $flushedKeys;
119
    }
120
121
    /**
122
     * Set the model cache lifetime.
123
     *
124
     * @param int $cacheLifetime
125
     *
126
     * @return $this
127
     */
128
    public function setCacheLifetime(int $cacheLifetime)
129
    {
130
        $this->cacheLifetime = $cacheLifetime;
0 ignored issues
show
The property cacheLifetime does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
131
132
        return $this;
133
    }
134
135
    /**
136
     * Get the model cache lifetime.
137
     *
138
     * @return int
139
     */
140
    public function getCacheLifetime(): int
141
    {
142
        return $this->cacheLifetime ?? -1;
143
    }
144
145
    /**
146
     * Set the model cache driver.
147
     *
148
     * @param string $cacheDriver
149
     *
150
     * @return $this
151
     */
152
    public function setCacheDriver($cacheDriver)
153
    {
154
        $this->cacheDriver = $cacheDriver;
0 ignored issues
show
The property cacheDriver does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
155
156
        return $this;
157
    }
158
159
    /**
160
     * Get the model cache driver.
161
     *
162
     * @return string|null
163
     */
164
    public function getCacheDriver(): ?string
165
    {
166
        return $this->cacheDriver ?? null;
167
    }
168
169
    /**
170
     * Determine if model cache clear is enabled.
171
     *
172
     * @return bool
173
     */
174
    public function isCacheClearEnabled(): bool
175
    {
176
        return $this->cacheClearEnabled ?? true;
0 ignored issues
show
The property cacheClearEnabled does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
177
    }
178
179
    /**
180
     * Forget the model cache.
181
     *
182
     * @return void
183
     */
184
    public static function forgetCache()
185
    {
186
        static::fireCacheFlushEvent('cache.flushing');
187
188
        // Flush cache tags
189
        if (method_exists(app('cache')->getStore(), 'tags')) {
190
            app('cache')->tags(static::class)->flush();
191
        } else {
192
            // Flush cache keys, then forget actual cache
193
            foreach (static::flushCacheKeys(static::class) as $cacheKey) {
194
                app('cache')->forget($cacheKey);
195
            }
196
        }
197
198
        static::fireCacheFlushEvent('cache.flushed', false);
199
    }
200
201
    /**
202
     * Fire the given event for the model.
203
     *
204
     * @param string $event
205
     * @param bool   $halt
206
     *
207
     * @return mixed
208
     */
209
    protected static function fireCacheFlushEvent($event, $halt = true)
210
    {
211
        if (! isset(static::$dispatcher)) {
212
            return true;
213
        }
214
215
        // We will append the names of the class to the event to distinguish it from
216
        // other model events that are fired, allowing us to listen on each model
217
        // event set individually instead of catching event for all the models.
218
        $event = "eloquent.{$event}: ".static::class;
219
220
        $method = $halt ? 'until' : 'dispatch';
221
222
        return static::$dispatcher->{$method}($event, static::class);
223
    }
224
225
    /**
226
     * Reset cached model to its defaults.
227
     *
228
     * @return $this
229
     */
230
    public function resetCacheConfig()
231
    {
232
        ! $this->cacheDriver || $this->cacheDriver = null;
233
        ! $this->cacheLifetime || $this->cacheLifetime = -1;
234
235
        return $this;
236
    }
237
238
    /**
239
     * Generate unique cache key.
240
     *
241
     * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder
242
     * @param array                                                                    $columns
243
     *
244
     * @return string
245
     */
246
    protected function generateCacheKey($builder, array $columns): string
247
    {
248
        $query = $builder instanceof Builder ? $builder->getQuery() : $builder;
249
        $vars = [
250
            'aggregate' => $query->aggregate,
251
            'columns' => $query->columns,
252
            'distinct' => $query->distinct,
253
            'from' => $query->from,
254
            'joins' => $query->joins,
255
            'wheres' => $query->wheres,
256
            'groups' => $query->groups,
257
            'havings' => $query->havings,
258
            'orders' => $query->orders,
259
            'limit' => $query->limit,
260
            'offset' => $query->offset,
261
            'unions' => $query->unions,
262
            'unionLimit' => $query->unionLimit,
263
            'unionOffset' => $query->unionOffset,
264
            'unionOrders' => $query->unionOrders,
265
            'lock' => $query->lock,
266
        ];
267
268
        return md5(json_encode([
269
            $vars,
270
            $columns,
271
            static::class,
272
            $this->getCacheDriver(),
273
            $this->getCacheLifetime(),
274
            $builder instanceof Builder ? $builder->getEagerLoads() : null,
275
            $builder->getBindings(),
0 ignored issues
show
The method getBindings does only exist in Illuminate\Database\Query\Builder, but not in Illuminate\Database\Eloquent\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
276
            $builder->toSql(),
0 ignored issues
show
The method toSql does only exist in Illuminate\Database\Query\Builder, but not in Illuminate\Database\Eloquent\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
277
        ]));
278
    }
279
280
    /**
281
     * Cache given callback.
282
     *
283
     * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder
284
     * @param array                                                                    $columns
285
     * @param \Closure                                                                 $closure
286
     *
287
     * @return mixed
288
     */
289
    public function cacheQuery($builder, array $columns, Closure $closure)
290
    {
291
        $modelName = $this->getMorphClass();
292
        $lifetime = $this->getCacheLifetime();
293
        $cacheKey = $this->generateCacheKey($builder, $columns);
294
295
        // Switch cache driver on runtime
296
        if ($driver = $this->getCacheDriver()) {
297
            app('cache')->setDefaultDriver($driver);
298
        }
299
300
        // We need cache tags, check if default driver supports it
301
        if (method_exists(app('cache')->getStore(), 'tags')) {
302
            $result = $lifetime === -1 ? app('cache')->tags($modelName)->rememberForever($cacheKey, $closure) : app('cache')->tags($modelName)->remember($cacheKey, $lifetime, $closure);
303
304
            return $result;
305
        }
306
307
        $result = $lifetime === -1 ? app('cache')->rememberForever($cacheKey, $closure) : app('cache')->remember($cacheKey, $lifetime, $closure);
308
309
        // Default cache driver doesn't support tags, let's do it manually
310
        static::storeCacheKey($modelName, $cacheKey);
311
312
        // We're done, let's clean up!
313
        $this->resetCacheConfig();
314
315
        return $result;
316
    }
317
318
    /**
319
     * Create a new Eloquent query builder for the model.
320
     *
321
     * @param \Illuminate\Database\Query\Builder $query
322
     *
323
     * @return \Illuminate\Database\Eloquent\Builder|static
324
     */
325
    public function newEloquentBuilder($query)
326
    {
327
        return new EloquentBuilder($query);
328
    }
329
}
330