*/ private array $values = array(); /** * Construct the store, optionally seeded with an initial set of * key/value pairs. * * Seeding is primarily useful for tests that need to exercise the * "existing environment" migration path, where the schema version * option is already populated before the code under test runs. * * @param array $seed Initial key/value pairs. */ public function __construct( array $seed = array() ) { foreach ( $seed as $key => $value ) { $this->values[ (string) $key ] = $value; } } /** * Read the value stored under the given key. * * Returns the caller-supplied fallback verbatim when the key is unset, * preserving its type (including `null`, `false`, and `0`). * * The parameter is named `$fallback` rather than `$default` to avoid * the PHP reserved keyword warning raised by PHPCS's * Universal.NamingConventions.NoReservedKeywordParameterNames sniff; * the semantics are identical to WordPress's `get_option( $key, $default )`. * * @param string $key Option key. * @param mixed $fallback Value returned when the key is unset. * * @return mixed The stored value, or `$fallback` when unset. */ public function get( string $key, $fallback = null ) { if ( ! array_key_exists( $key, $this->values ) ) { return $fallback; } return $this->values[ $key ]; } /** * Write the value under the given key (upsert). * * Returns true when the stored value changed as a result of the call * and false when the write was a no-op (identical value already * stored), matching WordPress's `update_option()` return convention. * * @param string $key Option key. * @param mixed $value Value to store. * * @return bool True when the stored value changed, false otherwise. */ public function update( string $key, $value ): bool { if ( array_key_exists( $key, $this->values ) && $this->values[ $key ] === $value ) { return false; } $this->values[ $key ] = $value; return true; } /** * Write the value under the given key without autoloading. * * The in-memory implementation has no autoload concept, so this delegates to * {@see self::update()}. * * @param string $key Option key. * @param mixed $value Value to store. */ public function updateNonAutoload( string $key, $value ): bool { return $this->update( $key, $value ); } /** * Delete the value stored under the given key. * * @param string $key Option key. * * @return bool True when a value existed and was removed, false when * no value was stored under the key. */ public function delete( string $key ): bool { if ( ! array_key_exists( $key, $this->values ) ) { return false; } unset( $this->values[ $key ] ); return true; } /** * Report whether a value is stored under the given key. * * Distinguishes "unset" from "stored as a falsy value" by checking * key presence directly, so callers can reliably probe for stored * `null` / `false` / `0` entries. * * @param string $key Option key. */ public function has( string $key ): bool { return array_key_exists( $key, $this->values ); } /** * @param string $key Option key. * @return array|null */ #[\Override] public function readAtomic( string $key ): ?array { if ( ! $this->has( $key ) ) { return null; } $value = $this->values[ $key ]; if ( ! is_array( $value ) ) { throw new \RuntimeException( 'Atomic option value must be an array.' ); } return $value; } /** * @param string $key Option key. * @param array|null $expected Expected value. * @param array $value Replacement value. */ #[\Override] public function compareAndSwap( string $key, ?array $expected, array $value ): bool { if ( $this->readAtomic( $key ) !== $expected ) { return false; } $this->values[ $key ] = $value; return true; } /** * Return every stored key/value pair. * * Exposed purely as a convenience for test assertions. Production * code MUST NOT rely on this method because it has no counterpart * on the {@see OptionStore} contract. * * @return array */ public function all(): array { return $this->values; } }