├── .dockerignore ├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── config.yml ├── dependabot.yml └── workflows │ ├── phpstan.yml │ ├── pint.yml │ └── tests.yml ├── .gitignore ├── .rr.yaml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── composer.json ├── config └── roadrunner.php ├── docker-compose.yml ├── phpstan.neon ├── phpunit.xml ├── pint.json ├── src ├── LaravelRoadRunnerCacheProvider.php ├── RoadRunnerCacheStore.php └── RoadRunnerFactory.php └── tests └── RoadRunnerCacheTest.php /.dockerignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | .DS_Store 3 | /composer.lock 4 | /.github 5 | /.idea 6 | /.git 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [phpstan.neon] 18 | indent_size = 2 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: asanikovich 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report an Issue or Bug with the Package 3 | title: "[Bug]: " 4 | labels: [ "bug" ] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | We're sorry to hear you have a problem. Can you help us solve it by providing the following details. 10 | - type: textarea 11 | id: what-happened 12 | attributes: 13 | label: What happened? 14 | description: What did you expect to happen? 15 | placeholder: I cannot currently do X thing because when I do, it breaks X thing. 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: how-to-reproduce 20 | attributes: 21 | label: How to reproduce the bug 22 | description: How did this occur, please add any config values used and provide a set of reliable steps if possible. 23 | placeholder: When I do X I see Y. 24 | validations: 25 | required: true 26 | - type: input 27 | id: package-version 28 | attributes: 29 | label: Package Version 30 | description: What version of our Package are you running? Please be as specific as possible 31 | placeholder: 1.0.0 32 | validations: 33 | required: true 34 | - type: input 35 | id: php-version 36 | attributes: 37 | label: PHP Version 38 | description: What version of PHP are you running? Please be as specific as possible 39 | placeholder: 8.2.0 40 | validations: 41 | required: true 42 | - type: input 43 | id: laravel-version 44 | attributes: 45 | label: Laravel Version 46 | description: What version of Laravel are you running? Please be as specific as possible 47 | placeholder: 9.0.0 48 | validations: 49 | required: true 50 | - type: textarea 51 | id: notes 52 | attributes: 53 | label: Notes 54 | description: Use this field to provide any other notes that you feel might be relevant to the issue. 55 | validations: 56 | required: false 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/asanikovich/laravel-roadrunner-cache/discussions/new?category=q-a 5 | about: Ask the community for help 6 | - name: Request a feature 7 | url: https://github.com/asanikovich/laravel-roadrunner-cache/discussions/new?category=ideas 8 | about: Share ideas for new features 9 | - name: Report a security issue 10 | url: https://github.com/asanikovich/laravel-roadrunner-cache/security/policy 11 | about: Learn how to notify us for sensitive bugs 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | labels: 12 | - "dependencies" 13 | -------------------------------------------------------------------------------- /.github/workflows/phpstan.yml: -------------------------------------------------------------------------------- 1 | name: Static code analysis 2 | 3 | on: 4 | push: 5 | paths: 6 | - "**.php" 7 | branches: [master, '*.x'] 8 | pull_request: 9 | branches: [master, '*.x'] 10 | 11 | jobs: 12 | phpstan: 13 | name: PHPStan 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | php: [ 8.2, 8.1 ] 21 | 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v3 25 | 26 | - name: Setup PHP 27 | uses: shivammathur/setup-php@v2 28 | with: 29 | php-version: ${{ matrix.php }} 30 | coverage: none 31 | 32 | - name: Install dependencies 33 | run: composer install --prefer-dist --no-interaction 34 | 35 | - name: Run PHPStan 36 | run: ./vendor/bin/phpstan analyse --memory-limit=2G --error-format=github 37 | -------------------------------------------------------------------------------- /.github/workflows/pint.yml: -------------------------------------------------------------------------------- 1 | name: Fix PHP code style issues 2 | 3 | on: 4 | push: 5 | paths: 6 | - "**.php" 7 | branches: [master, '*.x'] 8 | pull_request: 9 | branches: [master, '*.x'] 10 | 11 | jobs: 12 | php-code-styling: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v3 18 | with: 19 | ref: ${{ github.head_ref }} 20 | 21 | - name: Fix PHP code style issues 22 | uses: aglipanci/laravel-pint-action@2.3.0 23 | 24 | - name: Commit changes 25 | uses: stefanzweifel/git-auto-commit-action@v4 26 | with: 27 | commit_message: Fix styling 28 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | paths: 6 | - "**.php" 7 | branches: [master, '*.x'] 8 | pull_request: 9 | branches: [master, '*.x'] 10 | 11 | jobs: 12 | tests-rr: 13 | runs-on: ubuntu-22.04 14 | timeout-minutes: 5 15 | 16 | strategy: 17 | fail-fast: true 18 | matrix: 19 | php: [8.1, 8.2] 20 | laravel: [10] 21 | rr: [2023.1.4] 22 | 23 | name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - RR ${{ matrix.rr }} 24 | 25 | steps: 26 | - name: Checkout code 27 | uses: actions/checkout@v3 28 | 29 | - name: Setup PHP 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.php }} 33 | extensions: dom, curl, libxml, mbstring, zip, igbinary, sodium 34 | tools: composer:v2 35 | coverage: xdebug 36 | 37 | - name: Install laravel 38 | run: | 39 | composer create-project laravel/laravel laravel 40 | 41 | - name: Setup laravel with local laravel repository 42 | working-directory: ./laravel 43 | run: | 44 | composer config repositories.laravel-roadrunner-cache path ../. 45 | composer config minimum-stability dev 46 | composer require asanikovich/laravel-roadrunner-cache spiral/roadrunner-cli spiral/roadrunner-http laravel/octane 47 | 48 | - name: Copy config files 49 | run: cp .rr.yaml laravel/.rr.yaml 50 | 51 | - name: Install RoadRunner Binary 52 | working-directory: ./laravel 53 | run: | 54 | docker run --rm -v ${PWD}:/rootfs:rw --entrypoint "" spiralscout/roadrunner:${{ matrix.rr }} cp /usr/bin/rr /rootfs/rr 55 | 56 | - name: Install basic Composer dependencies 57 | run: composer install --prefer-dist --no-interaction --no-progress --ansi 58 | 59 | - name: Setup package 60 | working-directory: ./laravel 61 | run: php artisan vendor:publish --tag="laravel-roadrunner-cache-config" 62 | 63 | - name: Run RoadRunner 64 | run: ./laravel/rr serve -c .rr.yaml & 65 | env: 66 | APP_BASE_PATH: /home/runner/work/laravel-roadrunner-cache/laravel-roadrunner-cache/laravel 67 | LARAVEL_OCTANE: 1 68 | 69 | - name: Execute tests 70 | run: XDEBUG_MODE=coverage vendor/bin/phpunit 71 | 72 | - name: Upload coverage reports to Codecov 73 | uses: codecov/codecov-action@v3 74 | env: 75 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .phpunit.result.cache 3 | build 4 | composer.lock 5 | coverage 6 | vendor 7 | -------------------------------------------------------------------------------- /.rr.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | rpc: 3 | listen: 'tcp://127.0.0.1:6001' 4 | server: 5 | command: php ${APP_BASE_PATH}/vendor/bin/roadrunner-worker 6 | http: 7 | address: '127.0.0.1:8080' 8 | pool: 9 | num_workers: 1 10 | kv: 11 | memory: 12 | driver: memory 13 | config: 14 | interval: 1 15 | boltdb: 16 | driver: boltdb 17 | config: 18 | interval: 1 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-roadrunner-cachel` will be documented in this file. 4 | 5 | ## v1.0.0 - 2023-06-30 6 | 7 | Initial release for laravel 10 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.2-cli-alpine3.17 as backend 2 | 3 | RUN --mount=type=bind,from=mlocati/php-extension-installer:1.5,source=/usr/bin/install-php-extensions,target=/usr/local/bin/install-php-extensions \ 4 | install-php-extensions opcache zip xsl dom exif intl pcntl bcmath sockets igbinary sodium && \ 5 | apk del --no-cache ${PHPIZE_DEPS} ${BUILD_DEPENDS} 6 | 7 | RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS && \ 8 | apk add --update linux-headers && \ 9 | pecl install xdebug && \ 10 | docker-php-ext-enable xdebug && \ 11 | apk del -f .build-deps 12 | 13 | ENV COMPOSER_ALLOW_SUPERUSER=1 14 | COPY --from=composer:2.3 /usr/bin/composer /usr/bin/composer 15 | 16 | WORKDIR /app/lib 17 | 18 | COPY composer.json composer.json 19 | COPY src src 20 | COPY config config 21 | 22 | WORKDIR /app/laravel 23 | 24 | RUN composer create-project laravel/laravel /app/laravel 25 | RUN composer config repositories.laravel-roadrunner-cache path /app/lib && composer config minimum-stability dev 26 | RUN composer require asanikovich/laravel-roadrunner-cache spiral/roadrunner-cli spiral/roadrunner-http laravel/octane 27 | RUN composer install --optimize-autoloader --no-dev 28 | RUN php artisan vendor:publish --tag="laravel-roadrunner-cache-config" 29 | 30 | COPY .rr.yaml .rr.yaml 31 | COPY --from=ghcr.io/roadrunner-server/roadrunner:2023.1.1 /usr/bin/rr . 32 | 33 | WORKDIR /app/php 34 | 35 | COPY . . 36 | RUN composer install --optimize-autoloader 37 | 38 | EXPOSE 8080/tcp 39 | EXPOSE 6001/tcp 40 | 41 | # Run RoadRunner server 42 | CMD ["sh", "-c", "APP_BASE_PATH=/app/laravel /app/laravel/rr serve -c .rr.yaml -o http.address=0.0.0.0:8080 -o rpc.listen='tcp://0.0.0.0:6001'"] 43 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Aliaksei Sanikovich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel RoadRunner Cache 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/asanikovich/laravel-roadrunner-cache.svg?style=flat-square)](https://packagist.org/packages/asanikovich/laravel-roadrunner-cache) 4 | [![GitHub Tests Status](https://img.shields.io/github/actions/workflow/status/asanikovich/laravel-roadrunner-cache/tests.yml?branch=master&label=tests&style=flat-square)](https://github.com/asanikovich/laravel-roadrunner-cache/actions/workflows/tests.yml?query=branch%3Amaster) 5 | [![GitHub Tests Coverage Status](https://img.shields.io/codecov/c/github/asanikovich/laravel-roadrunner-cache?token=GXMKS36D91&style=flat-square)](https://github.com/asanikovich/laravel-roadrunner-cache/actions/workflows/tests.yml?query=branch%3Amaster) 6 | [![GitHub Code Style Status](https://img.shields.io/github/actions/workflow/status/asanikovich/laravel-roadrunner-cache/phpstan.yml?branch=master&label=code%20style&style=flat-square)](https://github.com/asanikovich/laravel-roadrunner-cache/actions/workflows/phpstan.yml?query=branch%3Amaster) 7 | [![GitHub Lint Status](https://img.shields.io/github/actions/workflow/status/asanikovich/laravel-roadrunner-cache/pint.yml?branch=master&label=lint&style=flat-square)](https://github.com/asanikovich/laravel-roadrunner-cache/actions/workflows/pint.yml?query=branch%3Amaster) 8 | [![Total Downloads](https://img.shields.io/packagist/dt/asanikovich/laravel-roadrunner-cache.svg?style=flat-square)](https://packagist.org/packages/asanikovich/laravel-roadrunner-cache) 9 | [![Licence](https://img.shields.io/packagist/l/asanikovich/laravel-roadrunner-cache.svg?style=flat-square)](https://packagist.org/packages/asanikovich/laravel-roadrunner-cache) 10 | 11 | **This Laravel package allows you to work with RoadRunner KV Cache in Laravel (as a cache driver).** 12 | 13 | ## For laravel/octane users ## 14 | 15 | Please note, that `swoole` in-memory or table cache works **only** inside HTTP workers. 16 | 17 | If you want a cache that works from all parts of PHP (HTTP or CLI) - our package and RoadRunner KV will help you. 18 | 19 | ## Getting Started 20 | 21 | ### Installing the Package 22 | 23 | You can install the package via composer: 24 | 25 | ```bash 26 | composer require asanikovich/laravel-roadrunner-cache 27 | ``` 28 | 29 | ### Configuration 30 | 31 | #### Configuration RoadRunner 32 | 33 | Make sure you have in your RoadRunner config file (`.rr.yaml`) next sections: 34 | - `RPC` section 35 | - `KV` section 36 | 37 | Full example of RoadRunner configuration file: 38 | ```yaml 39 | version: '3' 40 | rpc: 41 | listen: 'tcp://127.0.0.1:6001' 42 | server: 43 | command: php /var/www/html/vendor/bin/roadrunner-worker 44 | http: 45 | address: '127.0.0.1:8080' 46 | pool: 47 | num_workers: 1 48 | kv: 49 | memory: 50 | driver: memory 51 | config: 52 | interval: 1 53 | boltdb: 54 | driver: boltdb 55 | config: 56 | interval: 1 57 | ``` 58 | 59 | #### Publish config 60 | Publish the config file and setup RPC connection: 61 | 62 | ```bash 63 | php artisan vendor:publish --tag="laravel-roadrunner-cache-config" 64 | ``` 65 | 66 | #### Serializers 67 | 68 | Package supports next serializers (should be configured in `store` config): 69 | - `null` - default php `serializer` 70 | - `igbinary` - igbinary serializer, `ext-igbinary` is required to be installed 71 | 72 | #### Encryption 73 | 74 | Package supports encryption with `sodium`, requirements: 75 | - `ext-sodium` required to be installed 76 | - `encryption_key` filled in `store` config (generated by `sodium_crypto_box_keypair()`) 77 | 78 | #### Setup cache config file 79 | 80 | Add to cache configuration file (`/config/cache.php`) new store with driver `roadrunner`: 81 | 82 | #### Config file example 83 | ```php 84 | 'rr-memory', // Default store (optional) 87 | 88 | 'stores' => [ 89 | 'rr-memory' => [ // Custom store name with "memory" connection 90 | 'driver' => 'roadrunner', 91 | 'connection' => 'memory', // section name from KV plugin settings in RoadRunner config file (.rr.yaml) 92 | 'serializer' => null, // Available options: null|igbinary 93 | 'encryption_key' => null, // Available options: null|string 94 | ], 95 | 'rr-boltdb' => [ // Custom store name with "boltdb" connection (another store is optional) 96 | 'driver' => 'roadrunner', 97 | 'connection' => 'boltdb', // section name from KV plugin settings in RoadRunner config file (.rr.yaml) 98 | 'serializer' => null, // Available options: null|igbinary 99 | 'encryption_key' => null, // Available options: null|string 100 | 'prefix' => 'custom', // Custom prefix - non-empty-string 101 | ], 102 | 'rr-memory-igbinary' => [ // Custom store name with "memory" connection and "igbinary" serializer 103 | 'driver' => 'roadrunner', 104 | 'connection' => 'memory', // section name from KV plugin settings in RoadRunner config file (.rr.yaml) 105 | 'serializer' => 'igbinary', // Available options: null|igbinary 106 | 'encryption_key' => null, // Available options: null|string 107 | ], 108 | 'rr-memory-igbinary-encrypted' => [ // Custom store name with "memory" connection and encrypted "igbinary" serializer 109 | 'driver' => 'roadrunner', 110 | 'connection' => 'memory', // section name from KV plugin settings in RoadRunner config file (.rr.yaml) 111 | 'serializer' => 'igbinary', // Available options: null|igbinary 112 | 'encryption_key' => 'key1', // Available options: null|string 113 | ], 114 | 'rr-memory-encrypted' => [ // Custom store name with "memory" connection and encrypted serializer 115 | 'driver' => 'roadrunner', 116 | 'connection' => 'memory', // section name from KV plugin settings in RoadRunner config file (.rr.yaml) 117 | 'serializer' => null, // Available options: null|igbinary 118 | 'encryption_key' => 'key2', // Available options: null|string 119 | ], 120 | ], 121 | ] 122 | ``` 123 | 124 | ### Usage 125 | 126 | To use in your code: 127 | ```php 128 | get('key'); // Default main store - rr-memory 132 | Cache::driver('rr-boltdb')->get('key'); // rr-boltdb store 133 | ``` 134 | 135 | All done! 🚀 136 | 137 | ## Development 138 | Here are some useful commands for development 139 | 140 | Before running tests run docker-compose: 141 | ```bash 142 | docker-compose up -d 143 | ``` 144 | Run tests: 145 | ```bash 146 | composer run test 147 | ``` 148 | Run tests with coverage: 149 | ```bash 150 | composer run test-coverage 151 | ``` 152 | Perform type checking: 153 | ```bash 154 | composer run phpstan 155 | ``` 156 | Format your code: 157 | ```bash 158 | composer run format 159 | ``` 160 | 161 | ## Updates and Changes 162 | 163 | For details on updates and changes, please refer to our [CHANGELOG](CHANGELOG.md). 164 | 165 | ## License 166 | 167 | Laravel RoadRunner Cache is released under The MIT License (MIT). For more information, please see our [License File](LICENSE.md). 168 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asanikovich/laravel-roadrunner-cache", 3 | "description": "RoadRunner KV cache for laravel", 4 | "homepage": "https://github.com/asanikovich/laravel-roadrunner-cache", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Aliaksei Sanikovich", 9 | "email": "asanikovich@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^8.1", 14 | "laravel/framework": "^10.0", 15 | "spiral/roadrunner-kv": "^4.0" 16 | }, 17 | "require-dev": { 18 | "guzzlehttp/guzzle": "*", 19 | "laravel/pint": "^1.5", 20 | "nunomaduro/larastan": "^1.0|^2.4", 21 | "orchestra/testbench": "^8.0" 22 | }, 23 | "suggest": { 24 | "ext-igbinary": "(>3.1.6) Igbinary serailizer support", 25 | "ext-sodium": "Sodium encryption support" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "ASanikovich\\LaravelRoadRunnerCache\\": "src" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "ASanikovich\\LaravelRoadRunnerCache\\Tests\\": "tests" 35 | } 36 | }, 37 | "scripts": { 38 | "phpstan": "vendor/bin/phpstan analyse --memory-limit=2G", 39 | "test": "docker-compose exec rr /bin/sh -c 'vendor/bin/phpunit'", 40 | "test-coverage": "docker-compose exec rr /bin/sh -c 'XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text'", 41 | "format": "vendor/bin/pint" 42 | }, 43 | "config": { 44 | "sort-packages": true, 45 | "allow-plugins": { 46 | "pestphp/pest-plugin": true 47 | } 48 | }, 49 | "minimum-stability": "dev", 50 | "prefer-stable": true, 51 | "extra": { 52 | "laravel": { 53 | "providers": [ 54 | "ASanikovich\\LaravelRoadRunnerCache\\LaravelRoadRunnerCacheProvider" 55 | ] 56 | } 57 | }, 58 | "funding": [ 59 | { 60 | "url": "https://ko-fi.com/asanikovich", 61 | "type": "ko-fi" 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /config/roadrunner.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'host' => env('ROADRUNNER_RPC_HOST', '127.0.0.1'), 15 | 'port' => env('ROADRUNNER_RPC_PORT', 6001), 16 | ], 17 | ]; 18 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | services: 3 | rr: 4 | build: 5 | dockerfile: Dockerfile 6 | ports: 7 | - "8080:8080" 8 | - "6001:6001" 9 | environment: 10 | LARAVEL_OCTANE: 1 11 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - ./vendor/nunomaduro/larastan/extension.neon 3 | parameters: 4 | paths: 5 | - src 6 | - tests 7 | level: max 8 | checkMissingIterableValueType: true 9 | checkGenericClassInNonGenericObjectType: false 10 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | tests 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ./src 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "rules": { 4 | "use_arrow_functions": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/LaravelRoadRunnerCacheProvider.php: -------------------------------------------------------------------------------- 1 | publishes( 17 | [__DIR__.'/../config/roadrunner.php' => config_path('roadrunner.php')], 18 | 'laravel-roadrunner-cache-config' 19 | ); 20 | } 21 | 22 | public function boot(): void 23 | { 24 | $this->app->singleton(RPCInterface::class, static fn () => RoadRunnerFactory::createRPC()); 25 | 26 | Cache::extend('roadrunner', function (Application $app, array $config) { 27 | /** @var RPCInterface $rpc */ 28 | $rpc = $app->get(RPCInterface::class); 29 | $store = RoadRunnerFactory::createLaravelCacheStore($rpc, $config); 30 | 31 | return Cache::repository($store); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/RoadRunnerCacheStore.php: -------------------------------------------------------------------------------- 1 | prefix = ! empty($prefix) ? $prefix.':' : ''; 19 | } 20 | 21 | /** 22 | * @param string $key 23 | * {@inheritdoc} 24 | */ 25 | public function get($key) 26 | { 27 | try { 28 | return $this->storage->get($this->prefix.$key); 29 | } catch (InvalidArgumentException|Throwable) { 30 | return null; 31 | } 32 | } 33 | 34 | /** 35 | * @param string[] $keys 36 | * @return array 37 | * {@inheritdoc} 38 | * 39 | * @throws InvalidArgumentException 40 | */ 41 | public function many(array $keys): array 42 | { 43 | $keys = array_values($keys); 44 | $results = []; 45 | 46 | $keysPrefixes = array_map(fn (string $key) => $this->prefix.$key, $keys); 47 | 48 | $index = 0; 49 | foreach ($this->storage->getMultiple($keysPrefixes) as $value) { 50 | $results[$keys[$index++]] = $value; 51 | } 52 | 53 | return $results; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | * 59 | * @throws InvalidArgumentException 60 | */ 61 | public function put($key, $value, $seconds): bool 62 | { 63 | return $this->storage->set($this->prefix.$key, $value, $seconds); 64 | } 65 | 66 | /** 67 | * @param array $values 68 | * {@inheritdoc} 69 | * 70 | * @throws InvalidArgumentException 71 | */ 72 | public function putMany(array $values, $seconds): bool 73 | { 74 | $keys = array_keys($values); 75 | $keys = array_map(fn (string $key) => $this->prefix.$key, $keys); 76 | $values = array_combine($keys, $values); 77 | 78 | return $this->storage->setMultiple($values, $seconds); 79 | } 80 | 81 | /** 82 | * @param int $value 83 | * {@inheritdoc} 84 | * 85 | * @throws InvalidArgumentException 86 | */ 87 | public function increment($key, $value = 1): int 88 | { 89 | $value = (int) $value; 90 | 91 | $record = $this->get($key); 92 | 93 | if ($record === null) { 94 | $this->storage->set($this->prefix.$key, $value); 95 | 96 | return $value; 97 | } 98 | 99 | $value = (int) ($record + $value); 100 | $this->storage->set($this->prefix.$key, $value); 101 | 102 | return $value; 103 | } 104 | 105 | /** 106 | * {@inheritdoc} 107 | * 108 | * @throws InvalidArgumentException 109 | */ 110 | public function decrement($key, $value = 1): int 111 | { 112 | return $this->increment($key, $value * -1); 113 | } 114 | 115 | /** 116 | * {@inheritdoc} 117 | * 118 | * @throws InvalidArgumentException 119 | */ 120 | public function forever($key, $value): bool 121 | { 122 | return $this->storage->set($this->prefix.$key, $value); 123 | } 124 | 125 | /** 126 | * {@inheritdoc} 127 | * 128 | * @throws InvalidArgumentException 129 | */ 130 | public function forget($key): bool 131 | { 132 | return $this->storage->delete($this->prefix.$key); 133 | } 134 | 135 | /** 136 | * {@inheritdoc} 137 | */ 138 | public function flush(): bool 139 | { 140 | return $this->storage->clear(); 141 | } 142 | 143 | /** 144 | * {@inheritdoc} 145 | */ 146 | public function getPrefix(): string 147 | { 148 | return $this->prefix; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/RoadRunnerFactory.php: -------------------------------------------------------------------------------- 1 | $config 24 | */ 25 | public static function createLaravelCacheStore(RPCInterface $rpc, array $config): RoadRunnerCacheStore 26 | { 27 | if (($config['serializer'] ?? null) === 'igbinary') { 28 | $serializer = new IgbinarySerializer(); 29 | } else { 30 | $serializer = new DefaultSerializer(); 31 | } 32 | 33 | $encryptionKey = $config['encryption_key'] ?? null; 34 | if ($encryptionKey !== null) { 35 | $serializer = new SodiumSerializer($serializer, $encryptionKey); 36 | } 37 | 38 | $storage = (new Factory($rpc, $serializer))->select($config['connection']); 39 | 40 | /** @phpstan-ignore-next-line */ 41 | return new RoadRunnerCacheStore($storage, $config['prefix'] ?? config('cache.prefix', '')); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/RoadRunnerCacheTest.php: -------------------------------------------------------------------------------- 1 | key1 = sodium_crypto_box_keypair(); 23 | $this->key2 = sodium_crypto_box_keypair(); 24 | 25 | /** @phpstan-ignore-next-line */ 26 | $this->app['config']['cache'] = $this->getCacheConfig(); 27 | 28 | /** @phpstan-ignore-next-line */ 29 | $this->app['config']['roadrunner'] = [ 30 | 'rpc' => [ 31 | 'host' => env('ROADRUNNER_RPC_HOST', '127.0.0.1'), 32 | 'port' => env('ROADRUNNER_RPC_PORT', 6001), 33 | ], 34 | ]; 35 | } 36 | 37 | public static function cacheProvider(): Generator 38 | { 39 | yield ['rr-memory']; 40 | yield ['rr-boltdb', 'custom:']; 41 | yield ['rr-memory-igbinary']; 42 | yield ['rr-memory-igbinary-encrypted']; 43 | yield ['rr-memory-encrypted']; 44 | } 45 | 46 | /** 47 | * @dataProvider cacheProvider 48 | */ 49 | public function testCache(string $driver, string $prefix = null): void 50 | { 51 | $repository = Cache::driver($driver); 52 | 53 | $this->assertTrue($repository->clear()); 54 | 55 | $this->assertNull($repository->get('k')); 56 | $this->assertEquals(['k1' => null, 'k2' => null], $repository->getMultiple(['k1', 'k2'])); 57 | $this->assertTrue($repository->add('k1', 'v', 3600)); 58 | $this->assertEquals(['k1' => 'v', 'k2' => null], $repository->getMultiple(['k1', 'k2'])); 59 | 60 | /** @phpstan-ignore-next-line */ 61 | $this->assertTrue($repository->put(['k3' => 'v3', 'k4' => 'v4'], null)); 62 | $this->assertEquals(['k3' => 'v3', 'k4' => 'v4'], $repository->getMultiple(['k3', 'k4'])); 63 | 64 | $this->assertTrue($repository->setMultiple(['k3' => 'v31', 'k4' => 'v41'], 1)); 65 | /** @phpstan-ignore-next-line */ 66 | $this->assertEquals(['k3' => 'v31', 'k4' => 'v41'], $repository->get(['k3', 'k4'])); 67 | sleep(2); 68 | $this->assertEquals(['k3' => 0, 'k4' => 0], $repository->getMultiple(['k3', 'k4'], 0)); 69 | 70 | $this->assertTrue($repository->add('k5', 'v5', 3600)); 71 | $this->assertEquals('v5', $repository->get('k5')); 72 | $this->assertTrue($repository->delete('k5')); 73 | $this->assertEquals(null, $repository->get('k5')); 74 | $this->assertEquals('def', $repository->get('k5', 'def')); 75 | 76 | $this->assertEquals(1, $repository->increment('inc')); 77 | $this->assertEquals(1, $repository->get('inc')); 78 | $this->assertEquals(3, $repository->increment('inc', 2)); 79 | $this->assertEquals(4, $repository->increment('inc')); 80 | $this->assertEquals(4, $repository->get('inc')); 81 | $this->assertEquals(3, $repository->decrement('inc')); 82 | $this->assertEquals(1, $repository->decrement('inc', 2)); 83 | 84 | /** @phpstan-ignore-next-line */ 85 | $this->assertNull($repository->get((object) [])); 86 | 87 | $this->assertEquals($prefix ?? 'global-prefix:', $repository->getStore()->getPrefix()); 88 | 89 | $this->assertTrue($repository->clear()); 90 | } 91 | 92 | public function testServerHttp(): void 93 | { 94 | $httpClient = new Guzzle(['base_uri' => 'http://127.0.0.1:8080']); 95 | $response = $httpClient->send(new Request('GET', '/')); 96 | $this->assertSame(200, $response->getStatusCode()); 97 | $this->assertStringContainsString('Laravel', $body = (string) $response->getBody()); 98 | $this->assertStringContainsString('https://laravel.com/', $body); 99 | } 100 | 101 | protected function getPackageProviders($app): array 102 | { 103 | return [ 104 | LaravelRoadRunnerCacheProvider::class, 105 | ]; 106 | } 107 | 108 | /** 109 | * @see README.md for example configuration description 110 | * 111 | * @return array 112 | */ 113 | protected function getCacheConfig(): array 114 | { 115 | return [ 116 | 'default' => 'rr-memory', 117 | 118 | 'stores' => [ 119 | 'rr-memory' => [ 120 | 'driver' => 'roadrunner', 121 | 'connection' => 'memory', 122 | 'serializer' => null, 123 | 'encryption_key' => null, 124 | ], 125 | 'rr-boltdb' => [ 126 | 'driver' => 'roadrunner', 127 | 'connection' => 'boltdb', 128 | 'serializer' => null, 129 | 'encryption_key' => null, 130 | 'prefix' => 'custom', 131 | ], 132 | 'rr-memory-igbinary' => [ 133 | 'driver' => 'roadrunner', 134 | 'connection' => 'memory', 135 | 'serializer' => 'igbinary', 136 | 'encryption_key' => null, 137 | ], 138 | 'rr-memory-igbinary-encrypted' => [ 139 | 'driver' => 'roadrunner', 140 | 'connection' => 'memory', 141 | 'serializer' => 'igbinary', 142 | 'encryption_key' => $this->key1, 143 | ], 144 | 'rr-memory-encrypted' => [ 145 | 'driver' => 'roadrunner', 146 | 'connection' => 'memory', 147 | 'serializer' => null, 148 | 'encryption_key' => $this->key1, 149 | ], 150 | ], 151 | 152 | 'prefix' => 'global-prefix', 153 | ]; 154 | } 155 | } 156 | --------------------------------------------------------------------------------