From 4a4724014d90585452f7cba99815f9e29e0f333e Mon Sep 17 00:00:00 2001 From: Matthew Batchelder Date: Wed, 18 Dec 2024 15:52:42 -0500 Subject: [PATCH] Initial version --- .editorconfig | 19 +++ .env.testing | 57 +++++++ .env.testing.slic | 57 +++++++ .gitattributes | 6 + .github/workflows/compatibility.yml | 44 +++++ .github/workflows/phpcs.yml | 24 +++ .github/workflows/phpstan.yml | 26 +++ .github/workflows/tests-php.yml | 79 +++++++++ .gitignore | 22 +++ LICENSE | 2 +- README.md | 154 +++++++++++++++++- codeception.dist.yml | 14 ++ codeception.slic.yml | 3 + composer.json | 65 ++++++++ phpcs.xml.dist | 9 + phpstan.neon.dist | 33 ++++ src/Memoize/Config.php | 61 +++++++ .../Drivers/Contracts/DriverInterface.php | 41 +++++ src/Memoize/Drivers/MemoryDriver.php | 55 +++++++ src/Memoize/Drivers/WpCacheDriver.php | 87 ++++++++++ src/Memoize/Memoize.php | 53 ++++++ tests/_bootstrap.php | 1 + tests/_support/Helper/MemoizeTestCase.php | 8 + tests/_support/UnitTester.php | 26 +++ tests/config.php | 16 ++ tests/unit.suite.dist.yml | 20 +++ tests/unit/MemoizeTest.php | 106 ++++++++++++ tests/unit/_bootstrap.php | 1 + 28 files changed, 1086 insertions(+), 3 deletions(-) create mode 100644 .editorconfig create mode 100644 .env.testing create mode 100644 .env.testing.slic create mode 100644 .gitattributes create mode 100644 .github/workflows/compatibility.yml create mode 100644 .github/workflows/phpcs.yml create mode 100644 .github/workflows/phpstan.yml create mode 100644 .github/workflows/tests-php.yml create mode 100644 .gitignore create mode 100644 codeception.dist.yml create mode 100644 codeception.slic.yml create mode 100644 composer.json create mode 100644 phpcs.xml.dist create mode 100644 phpstan.neon.dist create mode 100644 src/Memoize/Config.php create mode 100644 src/Memoize/Drivers/Contracts/DriverInterface.php create mode 100644 src/Memoize/Drivers/MemoryDriver.php create mode 100644 src/Memoize/Drivers/WpCacheDriver.php create mode 100644 src/Memoize/Memoize.php create mode 100644 tests/_bootstrap.php create mode 100644 tests/_support/Helper/MemoizeTestCase.php create mode 100644 tests/_support/UnitTester.php create mode 100644 tests/config.php create mode 100644 tests/unit.suite.dist.yml create mode 100644 tests/unit/MemoizeTest.php create mode 100644 tests/unit/_bootstrap.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..184e64c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = true + +[*.{neon,neon.dist}] +indent_style = tab + +[**.{jshintrc,json,scss-lint,yml}] +indent_style = space +indent_size = 2 diff --git a/.env.testing b/.env.testing new file mode 100644 index 0000000..ac84e89 --- /dev/null +++ b/.env.testing @@ -0,0 +1,57 @@ +# This file will be consumed by both the CI and the tests. +# Some environment variables might not apply to one but might apply to the other: modify with care. + +# What version of WordPress we want to install and test against. +# This has to be compatible with the `wp core download` command, see https://developer.wordpress.org/cli/commands/core/download/. +WP_VERSION=latest + +# This is where, in the context of the CI, we'll install and configure WordPress. +# See `.travis.yml` for more information. +WP_ROOT_FOLDER=/tmp/wordpress + +# The WordPress installation will be served from the Docker container. +# See `dev/docker/ci-compose.yml` for more information. +WP_URL=http://localhost:8080 +WP_DOMAIN=localhost:8080 + +# The credentials that will be used to access the site in acceptance tests +# in methods like `$I->loginAsAdmin();`. +WP_ADMIN_USERNAME=admin +WP_ADMIN_PASSWORD=password + +WP_DB_PORT=4306 + +# The databse is served from the Docker `db` container. +# See `dev/docker/ci-compose.yml` for more information. +WP_TABLE_PREFIX=wp_ +WP_DB_HOST=127.0.0.1:4306 +WP_DB_NAME=wordpress +WP_DB_USER=root +WP_DB_PASSWORD= + +# The test databse is served from the Docker `db` container. +# See `dev/docker/ci-compose.yml` for more information. +WP_TEST_DB_HOST=127.0.0.1:4306 +WP_TEST_DB_NAME=test +WP_TEST_DB_USER=root +WP_TEST_DB_PASSWORD= + +# We're using Selenium and Chrome for acceptance testing. +# In CI context we're starting a Docker container to handle that. +# See the `dev/docker/ci-compose.yml` file. +CHROMEDRIVER_HOST=localhost +CHROMEDRIVER_PORT=4444 + +# The URL of the WordPress installation from the point of view of the Chromedriver container. +# Why not just use `wordpress`? While Chrome will accept an `http://wordpress` address WordPress +# will not, we call the WordPress container with a seemingly looking legit URL and leverage the +# lines that, in the `wp-config.php` file, will make it so that WordPress will use as its home +# URL whatever URL we reach it with. +# See the `dev/docker/wp-config.php` template for more information. +WP_CHROMEDRIVER_URL="wp.test" + +# To run the tests let's force the background-processing lib to run in synchronous (single PHP thread) mode. +TRIBE_NO_ASYNC=1 + +# We're using Docker to run the tests. +USING_CONTAINERS=1 diff --git a/.env.testing.slic b/.env.testing.slic new file mode 100644 index 0000000..20de04b --- /dev/null +++ b/.env.testing.slic @@ -0,0 +1,57 @@ +# This file will be consumed by both the CI and the tests. +# Some environment variables might not apply to one but might apply to the other: modify with care. + +# What version of WordPress we want to install and test against. +# This has to be compatible with the `wp core download` command, see https://developer.wordpress.org/cli/commands/core/download/. +WP_VERSION=latest + +# This is where, in the context of the CI, we'll install and configure WordPress. +# See `.travis.yml` for more information. +WP_ROOT_FOLDER=/var/www/html + +# The WordPress installation will be served from the Docker container. +# See `dev/docker/ci-compose.yml` for more information. +WP_URL=http://wordpress.test +WP_DOMAIN=wordpress.test + +# The credentials that will be used to access the site in acceptance tests +# in methods like `$I->loginAsAdmin();`. +WP_ADMIN_USERNAME=admin +WP_ADMIN_PASSWORD=password + +WP_DB_PORT=3306 + +# The databse is served from the Docker `db` container. +# See `dev/docker/ci-compose.yml` for more information. +WP_TABLE_PREFIX=wp_ +WP_DB_HOST=db +WP_DB_NAME=test +WP_DB_USER=root +WP_DB_PASSWORD=password + +# The test databse is served from the Docker `db` container. +# See `dev/docker/ci-compose.yml` for more information. +WP_TEST_DB_HOST=db +WP_TEST_DB_NAME=test +WP_TEST_DB_USER=root +WP_TEST_DB_PASSWORD=password + +# We're using Selenium and Chrome for acceptance testing. +# In CI context we're starting a Docker container to handle that. +# See the `dev/docker/ci-compose.yml` file. +CHROMEDRIVER_HOST=chrome +CHROMEDRIVER_PORT=4444 + +# The URL of the WordPress installation from the point of view of the Chromedriver container. +# Why not just use `wordpress`? While Chrome will accept an `http://wordpress` address WordPress +# will not, we call the WordPress container with a seemingly looking legit URL and leverage the +# lines that, in the `wp-config.php` file, will make it so that WordPress will use as its home +# URL whatever URL we reach it with. +# See the `dev/docker/wp-config.php` template for more information. +WP_CHROMEDRIVER_URL=http://wordpress.test + +# To run the tests let's force the background-processing lib to run in synchronous (single PHP thread) mode. +TRIBE_NO_ASYNC=1 + +# We're using Docker to run the tests. +USING_CONTAINERS=1 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..633e694 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text +.* export-ignore +composer.lock text -diff +phpstan.* export-ignore +phpunit.* export-ignore +tests export-ignore diff --git a/.github/workflows/compatibility.yml b/.github/workflows/compatibility.yml new file mode 100644 index 0000000..d977e28 --- /dev/null +++ b/.github/workflows/compatibility.yml @@ -0,0 +1,44 @@ +name: PHP Compatibility +on: + pull_request: + push: + branches: + - main + paths: + - '**.php' +jobs: + php-compatibility: + name: PHP ${{ matrix.php-version }} + runs-on: ubuntu-latest + strategy: + matrix: + php-version: ["7.4", "8.0", "8.1", "8.2"] + + steps: + - uses: actions/checkout@v4 + + - name: Configure PHP environment + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: mbstring, intl + coverage: none + + - name: Get Composer Cache Directory + id: composer-cache + run: | + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - uses: ramsey/composer-install@v3 + with: + dependency-versions: highest + + - name: Run PHP Compatibility + run: composer compatibility:php-${{ matrix.php-version }} diff --git a/.github/workflows/phpcs.yml b/.github/workflows/phpcs.yml new file mode 100644 index 0000000..da658af --- /dev/null +++ b/.github/workflows/phpcs.yml @@ -0,0 +1,24 @@ +name: Coding Standards +on: + pull_request: + push: + branches: + - main + paths: + - '**.php' +jobs: + phpcs: + name: phpcs + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Configure PHP environment + uses: shivammathur/setup-php@v2 + with: + php-version: '8.0' + - uses: ramsey/composer-install@v3 + with: + composer-options: "--ignore-platform-reqs --optimize-autoloader" + - name: Run PHPCS + run: ./vendor/bin/phpcs src diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..46fd118 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,26 @@ +name: PHPStan +on: + pull_request: + push: + branches: + - main + paths: + - '**.php' +jobs: + phpstsan: + name: phpstan + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Configure PHP environment + uses: shivammathur/setup-php@v2 + with: + php-version: '8.0' + extensions: mbstring, intl + coverage: none + - uses: ramsey/composer-install@v2 + with: + composer-options: "--ignore-platform-reqs --optimize-autoloader" + - name: Run PHPStan static analysis + run: composer test:analysis diff --git a/.github/workflows/tests-php.yml b/.github/workflows/tests-php.yml new file mode 100644 index 0000000..0066ab0 --- /dev/null +++ b/.github/workflows/tests-php.yml @@ -0,0 +1,79 @@ +name: 'Tests' +on: + pull_request: + push: + branches: + - main + paths: + - '**.php' +jobs: + test: + strategy: + matrix: + suite: + - unit + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v2 + with: + fetch-depth: 1000 + submodules: recursive + # ------------------------------------------------------------------------------ + # Checkout slic + # ------------------------------------------------------------------------------ + - name: Checkout slic + uses: actions/checkout@v2 + with: + repository: stellarwp/slic + ref: main + path: slic + fetch-depth: 1 + # ------------------------------------------------------------------------------ + # Prepare our composer cache directory + # ------------------------------------------------------------------------------ + - name: Get Composer Cache Directory + id: get-composer-cache-dir + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + - uses: actions/cache@v2 + id: composer-cache + with: + path: ${{ steps.get-composer-cache-dir.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + # ------------------------------------------------------------------------------ + # Initialize slic + # ------------------------------------------------------------------------------ + - name: Set up slic env vars + run: | + echo "SLIC_BIN=${GITHUB_WORKSPACE}/slic/slic" >> $GITHUB_ENV + echo "SLIC_WP_DIR=${GITHUB_WORKSPACE}/slic/_wordpress" >> $GITHUB_ENV + echo "SLIC_WORDPRESS_DOCKERFILE=Dockerfile.base" >> $GITHUB_ENV + - name: Set run context for slic + run: echo "SLIC=1" >> $GITHUB_ENV && echo "CI=1" >> $GITHUB_ENV + - name: Start ssh-agent + run: | + mkdir -p "${HOME}/.ssh"; + ssh-agent -a /tmp/ssh_agent.sock; + - name: Export SSH_AUTH_SOCK env var + run: echo "SSH_AUTH_SOCK=/tmp/ssh_agent.sock" >> $GITHUB_ENV + - name: Set up slic for CI + run: | + cd ${GITHUB_WORKSPACE}/.. + ${SLIC_BIN} here + ${SLIC_BIN} interactive off + ${SLIC_BIN} build-prompt off + ${SLIC_BIN} build-subdir off + ${SLIC_BIN} xdebug off + ${SLIC_BIN} debug on + ${SLIC_BIN} info + ${SLIC_BIN} config + ${SLIC_BIN} wp core update --version=6.4 --force + - name: Set up StellarWP Memoize + run: | + ${SLIC_BIN} use memoize + ${SLIC_BIN} composer install + - name: Run suites + run: ${SLIC_BIN} run ${{ matrix.suite }} --ext DotReporter diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b091f3d --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +composer.lock +files/ +repo/ +vendor/ +tests/_support/_generated +bin/*.phar + +# Dev tools +.buildpath +*.iml +.project +.idea/ +.vscode/ + +# Third party dependencies +vendor/ + +# Tests +codeception.yml +tests/_output/* +tests/*.suite.yml +!tests/_data/dump.sql diff --git a/LICENSE b/LICENSE index 11d43b4..28e1e52 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 StellarWP +Copyright (c) 2023 StellarWP Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 80b54bb..3ad6124 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,152 @@ -# memoize -A flexible memoization library +# Stellar Memoize + +[![Tests](https://github.com/stellarwp/memoize/workflows/Tests/badge.svg)](https://github.com/stellarwp/memoize/actions?query=branch%3Amain) [![Static Analysis](https://github.com/stellarwp/memoize/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/stellarwp/memoize/actions/workflows/static-analysis.yml) + +A flexible memoization library for memory caching. + +## Memoization + +[Memoization](https://en.wikipedia.org/wiki/Memoization) is the process of caching the results of expensive function calls so that they can be reused when the same inputs occur again. + +## Installation + +It's recommended that you install Memoize as a project dependency via [Composer](https://getcomposer.org/): + +```bash +composer require stellarwp/memoize +``` + +> We _actually_ recommend that this library gets included in your project using [Strauss](https://github.com/BrianHenryIE/strauss). +> +> Luckily, adding Strauss to your `composer.json` is only slightly more complicated than adding a typical dependency, so checkout our [strauss docs](https://github.com/stellarwp/global-docs/blob/main/docs/strauss-setup.md). + +## Notes on examples + +All namespaces within the examples will be using the `StellarWP\Memoize`, however, if you are using Strauss, you will need to prefix these namespaces with your project's namespace. + +## Usage + +### Simple example + +```php +use StellarWP\Memoize\Memoize; + +Memoize::set('foo', 'bar'); + +$value = Memoize::get('foo'); + +echo $value; // Outputs: bar +``` + +### Setting a nested structure + +Memoize allows you to use dot notation to set, get, and forget values from a nested structure. This allows you to easily add/fetch values and then purge individual items or whole collections of items. + +```php +use StellarWP\Memoize\Memoize; + +Memoize::set('foo.bar.bork', 'baz'); + +// This results in the following cache: +// [ +// 'foo' => [ +// 'bar' => [ +// 'bork' => 'baz', +// ], +// ], +// ] + +// You can fetch the value like so: +$value = Memoize::get('foo.bar.bork'); +echo $value; // Outputs: baz + +// You can fetch anywhere up the chain: +$value = Memoize::get('foo.bar'); +echo $value; // Outputs: [ 'bork' => 'baz' ] + +$value = Memoize::get('foo'); +echo $value; // Outputs: [ 'bar' => [ 'bork' => 'baz' ] ] + +$value = Memoize::get(); +echo $value; // Outputs: [ 'foo' => [ 'bar' => [ 'bork' => 'baz' ] ] ] +``` + +#### Purging a nested structure + +```php +use StellarWP\Memoize\Memoize; + +Memoize::set('foo.bar.bork', 'baz'); +Memoize::forget('foo.bar.bork'); + +// This results in the following cache: +// [ +// 'foo' => [ +// 'bar' => [], +// ], +// ] + +Memoize::forget('foo.bar'); + +// This results in the following cache: +// [ +// 'foo' => [], +// ] + +Memoize::forget('foo'); + +// This results in the following cache: +// [] + +Memoize::forget(); + +// This results in the following cache: +// [] +``` + +### Caching an expensive execution + +```php +use StellarWP\Memoize\Memoize; + +function my_expensive_function() { + $key = __FUNCTION__; + $value = Memoize::get($key); + + if ( ! $value ) { + // Do some crazy expensive stuff to set: + + $value = $thing; + + Memoize::set($key, $value); + } + + return $value; +} +``` + +## Drivers + +Memoize comes with a few drivers out of the box, but you can also create your own. + +### MemoryDriver + +The `MemoryDriver` is a simple in-memory driver is in-memory memoization. Basically, there's a static variable in the driver that holds the memoized values. You can manually specify the use of this driver like so: + +```php +use StellarWP\Memoize\Config; +use StellarWP\Memoize\Drivers\MemoryDriver; + +Config::setDriver(new MemoryDriver()); +``` + +### WpCacheDriver + +The `WpCacheDriver` is the default driver and uses WordPress's `wp_cache_set` and `wp_cache_get` functions and stores all of the memoized values in a single cache entry. Getting and setting memoized values is done by fetching from cache, manipulating (if needed), and saving back to cache (if needed). + +```php +use StellarWP\Memoize\Config; +use StellarWP\Memoize\Drivers\WpCacheDriver; + +Config::setDriver(new WpCacheDriver()); +``` diff --git a/codeception.dist.yml b/codeception.dist.yml new file mode 100644 index 0000000..dc50b32 --- /dev/null +++ b/codeception.dist.yml @@ -0,0 +1,14 @@ +actor: Tester +bootstrap: _bootstrap.php +paths: + tests: tests + log: tests/_output + data: tests/_data + helpers: tests/_support + wp_root: "%WP_ROOT_FOLDER%" +settings: + colors: true + memory_limit: 1024M +params: + # read dynamic configuration parameters from the .env file + - .env.testing diff --git a/codeception.slic.yml b/codeception.slic.yml new file mode 100644 index 0000000..9bcf605 --- /dev/null +++ b/codeception.slic.yml @@ -0,0 +1,3 @@ +params: + # read dynamic configuration parameters from the .env file + - .env.testing.slic diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4482ea3 --- /dev/null +++ b/composer.json @@ -0,0 +1,65 @@ +{ + "name": "stellarwp/memoize", + "description": "A flexible memoization library for memory caching.", + "type": "library", + "license": "GPL-2.0", + "platform": { + "php": "7.4" + }, + "autoload": { + "psr-4": { + "StellarWP\\Memoize\\": "src/Memoize/" + } + }, + "autoload-dev": { + "psr-4": { + "StellarWP\\Memoize\\Tests\\": "tests/", + "StellarWP\\Memoize\\Tests\\Helper\\": "tests/_support/Helper/", + "StellarWP\\Memoize\\Tests\\Support\\": "tests/_support/Support/" + } + }, + "authors": [ + { + "name": "StellarWP", + "email": "dev@stellarwp.com" + } + ], + "minimum-stability": "stable", + "require": { + "stellarwp/arrays": "^1.2" + }, + "require-dev": { + "lucatume/wp-browser": "^3.7", + "php-stubs/wordpress-stubs": "6.7.1", + "phpcompatibility/phpcompatibility-wp": "^2.1", + "stellarwp/coding-standards": "^2.0", + "symfony/event-dispatcher-contracts": "^2.5.1", + "symfony/string": "^5.4", + "szepeviktor/phpstan-wordpress": "^1.1" + }, + "scripts": { + "lint": "vendor/bin/phpcs", + "format": "vendor/bin/phpcbf", + "compatibility:php-7.4": "phpcs ./src -s --standard=PHPCompatibilityWP --runtime-set testVersion 7.4", + "compatibility:php-8.0": "phpcs ./src -s --standard=PHPCompatibilityWP --runtime-set testVersion 8.0", + "compatibility:php-8.1": "phpcs ./src -s --standard=PHPCompatibilityWP --runtime-set testVersion 8.1", + "compatibility:php-8.2": "phpcs ./src -s --standard=PHPCompatibilityWP --runtime-set testVersion 8.2", + "compatibility": [ + "@compatibility:php-7.4", + "@compatibility:php-8.0", + "@compatibility:php-8.1", + "@compatibility:php-8.2" + ], + "test:analysis": [ + "phpstan analyse -c phpstan.neon.dist --memory-limit=512M" + ] + }, + "scripts-descriptions": { + "test:analysis": "Run static code analysis." + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..e401b33 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + */tests/* + */vendor/* + + */common/* + diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..1b3c906 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,33 @@ +# Configuration for PHPStan +# https://phpstan.org/config-reference + +includes: + # @see https://github.com/phpstan/phpstan-src/blob/master/conf/bleedingEdge.neon + - phar://phpstan.phar/conf/bleedingEdge.neon + # Include this extension + - vendor/szepeviktor/phpstan-wordpress/extension.neon + +parameters: + parallel: + jobSize: 10 + maximumNumberOfProcesses: 32 + minimumNumberOfJobsPerProcess: 2 + level: 5 + inferPrivatePropertyTypeFromConstructor: true + reportUnmatchedIgnoredErrors: false + treatPhpDocTypesAsCertain: false + + # Paths to be analyzed. + paths: + - %currentWorkingDirectory%/src + + ignoreErrors: + # Uses func_get_args() + - '#^Function add_query_arg invoked with [123] parameters?, 0 required\.$#' + # Uses func_get_args() + - '#^Function apply_filters(_ref_array)? invoked with [34567] parameters, 2 required\.$#' + - '#^Constant WP_CONTENT_DIR not found\.$#' + - '#^Constant WP_CONTENT_URL not found\.$#' + - '#^Constant WPMU_PLUGIN_URL not found\.$#' + - '#^Constant WP_PLUGIN_DIR not found\.$#' + - '#^Constant WP_PLUGIN_URL not found\.$#' diff --git a/src/Memoize/Config.php b/src/Memoize/Config.php new file mode 100644 index 0000000..4cd0fcd --- /dev/null +++ b/src/Memoize/Config.php @@ -0,0 +1,61 @@ +cacheKey; + } + + /** + * @return array + */ + protected function getWpCache(): array + { + $cache = wp_cache_get($this->getCacheKey()); + + if (!$cache) { + $cache = []; + } + + return $cache; + } + + /** + * @inheritDoc + */ + public function get(?string $key = null) + { + $cache = $this->getWpCache(); + + if (!$key) { + return $cache; + } + + return Arr::get($cache, $key); + } + + /** + * @inheritDoc + */ + public function set(string $key, $value): void + { + $cache = $this->getWpCache(); + $cache = Arr::add($cache, $key, $value); + wp_cache_set($this->getCacheKey(), $cache); + } + + /** + * @inheritDoc + */ + public function has(string $key): bool + { + $cache = $this->getWpCache(); + return Arr::has($cache, $key); + } + + /** + * @inheritDoc + */ + public function forget(?string $key = null): void + { + $cache = $this->getWpCache(); + + if ($key) { + Arr::forget($cache, $key); + } else { + $cache = []; + } + + wp_cache_set($this->getCacheKey(), $cache); + } +} diff --git a/src/Memoize/Memoize.php b/src/Memoize/Memoize.php new file mode 100644 index 0000000..0dd8031 --- /dev/null +++ b/src/Memoize/Memoize.php @@ -0,0 +1,53 @@ +get($key); + } + + /** + * Set a value in the memoization cache. + * + * @param string $key The cache key using dot notation. + * @param mixed $value The value to store in the cache. + * @return void + */ + public static function set(string $key, $value): void + { + Config::getDriver()->set($key, $value); + } + + /** + * Check if a key exists in the memoization cache. + * + * @param string $key The cache key using dot notation. + * @return boolean + */ + public static function has(string $key): bool + { + return Config::getDriver()->has($key); + } + + /** + * Remove a key from the memoization cache. + * + * @param ?string $key The cache key using dot notation. If null, the entire cache will be cleared. + * @return void + */ + public static function forget(?string $key = null): void + { + Config::getDriver()->forget($key); + } +} diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/tests/_bootstrap.php @@ -0,0 +1 @@ + getenv( 'WP_ROOT_FOLDER' ) . '/wp-content', + 'ABSPATH' => getenv( 'WP_ROOT_FOLDER' ) . '/', +]; + +foreach ( $constants as $key => $value ) { + if ( defined( $key ) ) { + continue; + } + + define( $key, $value ); +} diff --git a/tests/unit.suite.dist.yml b/tests/unit.suite.dist.yml new file mode 100644 index 0000000..cdcf2d4 --- /dev/null +++ b/tests/unit.suite.dist.yml @@ -0,0 +1,20 @@ +# Codeception Test Suite Configuration + +# suite for WordPress functional tests. +# Emulate web requests and make application process them. +class_name: UnitTester +bootstrap: _bootstrap.php +modules: + enabled: + - WPLoader + config: + WPLoader: + wpRootFolder: "%WP_ROOT_FOLDER%" + dbName: "%WP_TEST_DB_NAME%" + dbHost: "%WP_TEST_DB_HOST%" + dbUser: "%WP_TEST_DB_USER%" + dbPassword: "%WP_TEST_DB_PASSWORD%" + tablePrefix: "%WP_TABLE_PREFIX%" + domain: "%WP_DOMAIN%" + title: stellarwp/memoize tests + configFile: tests/config.php diff --git a/tests/unit/MemoizeTest.php b/tests/unit/MemoizeTest.php new file mode 100644 index 0000000..92fe318 --- /dev/null +++ b/tests/unit/MemoizeTest.php @@ -0,0 +1,106 @@ + ['driver' => new MemoryDriver()], + 'WpCacheDriver' => ['driver' => new WpCacheDriver()], + ]; + } + + /** + * @dataProvider memoizationDrivers + */ + public function testSetsSimpleValue($driver) + { + Config::setDriver($driver); + Memoize::set('foo', 'bar'); + $this->assertEquals('bar', Memoize::get('foo')); + } + + /** + * @dataProvider memoizationDrivers + */ + public function testSetsDeepValue($driver) + { + Config::setDriver($driver); + Memoize::set('foo.bar.bork.blarg.moo', 'baz'); + $this->assertEquals('baz', Memoize::get('foo.bar.bork.blarg.moo')); + } + + /** + * @dataProvider memoizationDrivers + */ + public function testForgetsEverything($driver) + { + Config::setDriver($driver); + Memoize::set('foo.bar.bork.blarg.moo', 'baz'); + Memoize::forget(); + $this->assertFalse(Memoize::has('foo.bar.bork.blarg.moo')); + $this->assertFalse(Memoize::has('foo.bar.bork.blarg')); + $this->assertFalse(Memoize::has('foo.bar.bork')); + $this->assertFalse(Memoize::has('foo.bar')); + $this->assertFalse(Memoize::has('foo')); + } + + /** + * @dataProvider memoizationDrivers + */ + public function testForgetsLeaves($driver) + { + Config::setDriver($driver); + Memoize::set('foo.bar.bork.blarg.moo', 'baz'); + Memoize::set('foo.bar.bork.blarg.oink', 'lol'); + Memoize::forget('foo.bar.bork.blarg.moo'); + $this->assertFalse(Memoize::has('foo.bar.bork.blarg.moo')); + $this->assertTrue(Memoize::has('foo.bar.bork.blarg.oink')); + } + + /** + * @dataProvider memoizationDrivers + */ + public function testForgetsBranches($driver) + { + Config::setDriver($driver); + Memoize::set('foo.bar.bork.blarg.moo', 'baz'); + Memoize::set('foo.bar.bork.blarg.oink', 'lol'); + Memoize::forget('foo.bar.bork'); + $this->assertFalse(Memoize::has('foo.bar.bork')); + $this->assertTrue(Memoize::has('foo.bar')); + } + + /** + * @dataProvider memoizationDrivers + */ + public function testForgetsSimpleValues($driver) + { + Config::setDriver($driver); + Memoize::set('foo', 'baz'); + Memoize::set('bork', 'lol'); + Memoize::forget('foo'); + $this->assertFalse(Memoize::has('foo')); + $this->assertTrue(Memoize::has('bork')); + } + +} + diff --git a/tests/unit/_bootstrap.php b/tests/unit/_bootstrap.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/tests/unit/_bootstrap.php @@ -0,0 +1 @@ +