Skip to content

Commit efc777e

Browse files
First commit.
0 parents  commit efc777e

18 files changed

+1261
-0
lines changed

.editorconfig

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
; This file is for unifying the coding style for different editors and IDEs.
2+
; More information at http://editorconfig.org
3+
4+
root = true
5+
6+
[*]
7+
charset = utf-8
8+
indent_size = 4
9+
indent_style = space
10+
end_of_line = lf
11+
insert_final_newline = true
12+
trim_trailing_whitespace = true
13+
14+
[*.md]
15+
trim_trailing_whitespace = false
16+
17+
[*.yml]
18+
indent_size = 2

.gitattributes

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Path-based git attributes
2+
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
3+
4+
# Ignore all test and documentation with "export-ignore".
5+
/.gitattributes export-ignore
6+
/.gitignore export-ignore
7+
/.travis.yml export-ignore
8+
/phpunit.xml.dist export-ignore
9+
/.scrutinizer.yml export-ignore
10+
/tests export-ignore
11+
/.editorconfig export-ignore

.github/FUNDING.yml

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Help me support this package
2+
3+
ko_fi: DarkGhostHunter
4+
custom: ['https://paypal.me/darkghosthunter']

.github/dependabot.yml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: composer
4+
directory: "/"
5+
schedule:
6+
interval: daily
7+
time: "09:00"
8+
open-pull-requests-limit: 10

.github/workflows/php.yml

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
test:
9+
10+
runs-on: ubuntu-latest
11+
strategy:
12+
fail-fast: true
13+
matrix:
14+
php: [ 8.0, 8.1 ]
15+
laravel: [ 9.* ]
16+
dependency-version: [ prefer-lowest, prefer-stable ]
17+
include:
18+
- laravel: 9.*
19+
testbench: 7.*
20+
21+
name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.dependency-version }}
22+
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v2
26+
27+
- name: Setup PHP
28+
uses: shivammathur/setup-php@v2
29+
with:
30+
php-version: ${{ matrix.php }}
31+
extensions: mbstring, intl
32+
coverage: xdebug
33+
34+
- name: Cache dependencies
35+
uses: actions/cache@v2
36+
with:
37+
path: ~/.composer/cache/files
38+
key: ${{ runner.os }}-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
39+
restore-keys: ${{ runner.os }}-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-
40+
41+
- name: Install dependencies
42+
run: |
43+
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-progress --no-update
44+
composer update --${{ matrix.dependency-version }} --prefer-dist --no-progress
45+
- name: Run Tests
46+
run: composer run-script test
47+
48+
- name: Upload Coverage to Coveralls
49+
env:
50+
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51+
COVERALLS_SERVICE_NAME: github
52+
run: |
53+
rm -rf composer.* vendor/
54+
composer require php-coveralls/php-coveralls
55+
vendor/bin/php-coveralls

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
build
2+
composer.lock
3+
docs
4+
vendor
5+
coverage
6+
.idea
7+
/.phpunit.result.cache
8+
/phpunit.xml.dist.bak

.stubs/stubs.php

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Illuminate\Database\Query
4+
{
5+
6+
use DateInterval;
7+
use DateTimeInterface;
8+
9+
class Builder
10+
{
11+
/**
12+
* Caches the underlying builder results.
13+
*
14+
* @param int|\DateTimeInterface|\DateInterval $ttl
15+
* @param string $key
16+
* @param string|null $store
17+
* @param int $wait
18+
* @return $this
19+
*/
20+
public function cache(int|DateTimeInterface|DateInterval $ttl = 60, string $key = '', string $store = null, int $wait = 0): static
21+
{
22+
//
23+
}
24+
}
25+
}
26+
27+
namespace Illuminate\Database\Eloquent
28+
{
29+
30+
use DateInterval;
31+
use DateTimeInterface;
32+
33+
class Builder
34+
{
35+
/**
36+
* Caches the underlying builder results.
37+
*
38+
* @param int|\DateTimeInterface|\DateInterval $ttl
39+
* @param string $key
40+
* @param string|null $store
41+
* @param int $wait
42+
* @return \Illuminate\Database\Query\Builder
43+
*/
44+
public function cache(int|DateTimeInterface|DateInterval $ttl = 60, string $key = '', string $store = null, int $wait = 0): static
45+
{
46+
//
47+
}
48+
}
49+
}

README.md

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Cache Query
2+
3+
Remember your query results using only one method. Yes, only one.
4+
5+
```php
6+
Articles::latest('published_at')->cache()->take(10)->get();
7+
```
8+
9+
## Requirements
10+
11+
* PHP 8.0
12+
* Laravel 9.x
13+
14+
## Installation
15+
16+
You can install the package via composer:
17+
18+
```bash
19+
composer require laragear/cache-query
20+
```
21+
22+
## Usage
23+
24+
Just use the `cache()` method to remember the results of a query for a default of 60 seconds.
25+
26+
```php
27+
use Illuminate\Support\Facades\DB;
28+
use App\Models\Article;
29+
30+
$database = DB::table('articles')->latest('published_at')->take(10)->cache()->get();
31+
32+
$eloquent = Article::latest('published_at')->take(10)->cache()->get();
33+
```
34+
35+
The next time you call the **same** query, the result will be retrieved from the cache instead of running the SQL statement in the database, even if the result is empty, `null` or `false`.
36+
37+
Since it's [eager load unaware](#eager-load-unaware), you can also cache (or not) an eager loaded relation.
38+
39+
```php
40+
use App\Models\User;
41+
42+
$eloquent = User::where('is_author')->with('posts' => function ($posts) {
43+
$post->cache()->where('published_at', '>', now());
44+
})->paginate();
45+
```
46+
47+
### Time-to-live
48+
49+
By default, results of a query are cached by 60 seconds, but you're free to use any length, `Datetime`, `DateInterval` or Carbon instance.
50+
51+
```php
52+
use Illuminate\Support\Facades\DB;
53+
use App\Models\Article;
54+
55+
DB::table('articles')->latest('published_at')->take(10)->cache(120)->get();
56+
57+
Article::latest('published_at')->take(10)->cache(now()->addHour())->get();
58+
```
59+
60+
### Custom Cache Key
61+
62+
The auto-generated cache key is an BASE64-MD5 hash of the SQL query and its bindings, which avoids any collision with other queries while keeping the cache key short for a faster lookup in the cache store.
63+
64+
```php
65+
Article::latest('published_at')->take(10)->cache(30, 'latest_articles')->get();
66+
```
67+
68+
You can use this to your advantage to manually retrieve the result across your application:
69+
70+
```php
71+
$cachedArticles = Cache::get('cache-query|latest_articles');
72+
```
73+
74+
### Custom Cache Store
75+
76+
You can use any other Cache Store different from the application default by setting a third parameter, or a named parameter.
77+
78+
```php
79+
Article::latest('published_at')->take(10)->cache(store: 'redis')->get();
80+
```
81+
82+
### Cache Lock (data races)
83+
84+
On multiple processes, the query may be executed multiple times until the first process is able to store the result in the cache, specially when these take more than one second. To avoid this, set the `wait` parameter with the number of seconds to hold the acquired lock.
85+
86+
```php
87+
Article::latest('published_at')->take(200)->cache(wait: 5)->get();
88+
```
89+
90+
The first process will acquire the lock for the given seconds and execute the query. The next processes will wait the same amount of seconds until the first process stores the result in the cache to retrieve it.
91+
92+
> If you need to use this across multiple processes, use the [cache lock](https://laravel.com/docs/cache#managing-locks-across-processes) directly.
93+
94+
### Idempotent queries
95+
96+
While the reason behind remembering a Query is to cache the data retrieved from a database, you can use this to your advantage to create [idempotent](https://en.wikipedia.org/wiki/Idempotence) queries.
97+
98+
For example, you can make this query only execute once every day for a given user ID.
99+
100+
```php
101+
$key = auth()->user()->getAuthIdentifier();
102+
103+
Article::whereKey(54)->cache(now()->addHour(), "user:$key")->increment('unique_views');
104+
```
105+
106+
Subsequent executions of this query won't be executed at all until the cache expires, so in the above example we have surprisingly created a "unique views" mechanic.
107+
108+
## Caveats
109+
110+
This cache package does some clever things to always retrieve the data from the cache, or populate it with the results, in an opaque way and using just one method, but this world is far from perfect.
111+
112+
### Operations are **NOT** commutative
113+
114+
Altering the Builder methods order will change the auto-generated cache key. Even if two or more queries are _visually_ the same, the order of statements makes the hash completely different.
115+
116+
For example, given two similar queries in different parts of the application, these both will **not** share the same cached result:
117+
118+
```php
119+
User::query()->cache()->whereName('Joe')->whereAge(20)->first();
120+
// Cache key: "query-cache|/XreUO1yaZ4BzH2W6LtBSA=="
121+
122+
User::query()->cache()->whereAge(20)->whereName('Joe')->first();
123+
// Cache key: "query-cache|muDJevbVppCsTFcdeZBxsA=="
124+
```
125+
126+
To ensure you're hitting the same cache on similar queries, use a [custom cache key](#custom-cache-key). With this, all queries using the same key will share the same cached result:
127+
128+
```php
129+
User::query()->cache(60, 'find_joe')->whereName('Joe')->whereAge(20)->first();
130+
User::query()->cache(60, 'find_joe')->whereAge(20)->whereName('Joe')->first();
131+
```
132+
133+
### Eager load **unaware**
134+
135+
Since caching only works for the current query builder instance, an underlying Eager Load query won't be cached.
136+
137+
```php
138+
$page = 1;
139+
140+
User::with('posts', function ($posts) use ($page) {
141+
return $posts()->forPage($page);
142+
})->cache()->find(1);
143+
```
144+
145+
In the example, the `posts` eager load query results are never cached. To avoid that, you can use `cache()` on the eager loaded query. This way both the parent `user` query and the child `posts` query will be saved into the cache.
146+
147+
```php
148+
$page = 1;
149+
150+
User::with('posts', function ($posts) use ($page) {
151+
return $posts()->cache()->forPage($page);
152+
})->find(1);
153+
```
154+
155+
## How it works?
156+
157+
When you use `cache()`, it will wrap the base builder into a `CacheAwareProxy` proxy calls to it. At the same time, it injects a callback that runs _before_ is sent to the database for execution.
158+
159+
This callback checks if the results are in the cache. On cache hit, it throws an exception to interrupt the query, which is recovered by the `CacheAwareProxy`, returning the results.
160+
161+
For the Eloquent Builder, this wraps happens below it, so all calls pass through the `CacheAwareProxy` before hitting the real base builder.
162+
163+
## Security
164+
165+
If you discover any security related issues, please email darkghosthunter@gmail.com instead of using the issue tracker.

composer.json

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"name": "laragear/query-cache",
3+
"description": "description",
4+
"keywords": [
5+
"laravel",
6+
"database",
7+
"query",
8+
"cache"
9+
],
10+
"homepage": "https://github.com/laragear/query-cache",
11+
"minimum-stability": "stable",
12+
"prefer-stable": true,
13+
"license": "proprietary",
14+
"type": "library",
15+
"authors": [
16+
{
17+
"name": "Italo Israel Baeza Cabrera",
18+
"email": "darkghosthunter@gmail.com",
19+
"homepage": "https://patreon.com/packagesforlaravel"
20+
}
21+
],
22+
"require": {
23+
"php": "^8.0.2",
24+
"illuminate/cache": "9.*",
25+
"illuminate/config": "9.*",
26+
"illuminate/database": "9.*",
27+
"illuminate/support": "9.*",
28+
"illuminate/container": "9.*",
29+
"illuminate/contracts": "9.*"
30+
},
31+
"require-dev": {
32+
"orchestra/testbench": "7.*",
33+
"phpunit/phpunit": "^9.5"
34+
},
35+
"autoload": {
36+
"psr-4": {
37+
"Laragear\\CacheQuery\\": "src"
38+
}
39+
},
40+
"autoload-dev": {
41+
"psr-4": {
42+
"Tests\\": "tests"
43+
}
44+
},
45+
"scripts": {
46+
"test": "vendor/bin/phpunit --coverage-clover build/logs/clover.xml",
47+
"test-coverage": "vendor/bin/phpunit --coverage-html coverage"
48+
49+
},
50+
"config": {
51+
"sort-packages": true
52+
},
53+
"extra": {
54+
"laravel": {
55+
"providers": [
56+
"Laragear\\CacheQuery\\CacheQueryServiceProvider"
57+
]
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)