*/ protected ?array $config = null; /** * @var Container */ protected Container $container; /** * @param Container $container The container. */ public function __construct( Container $container ) { $this->container = $container; } /** * {@inheritdoc} */ public function get( string $key, ?Closure $next = null ) { $config = $this->fetch(); $value = Arr::get( $config, explode( '.', $key ) ); if ( isset( $value ) ) { return $value; } return $next === null ? null : $next( $key ); } /** * {@inheritdoc} */ public function save( array $changes, ?Closure $next = null ) { // Filter the current changes down to only the ones this strategy is responsible for. $responsible_changes = array_filter( $changes, function ( $key ) { return $this->is_responsible_for( $key ); }, ARRAY_FILTER_USE_KEY ); $expanded_changes = []; // Expand array back into multidimensional array. foreach ( $responsible_changes as $key => $value ) { $expanded_changes = Arr::set( $expanded_changes, explode( '.', $key ), $value ); } // Save the changes in one transaction. if ( count( $expanded_changes ) > 0 ) { $config = $this->fetch(); $updated_changes = array_merge( $config, $expanded_changes ); $option_key = Config::OPTION_KEY; remove_action( "update_option_{$option_key}", $this->container->callback( Provider::class, 'save_via_config' ) ); update_option( Config::OPTION_KEY, $updated_changes ); add_action( "update_option_{$option_key}", $this->container->callback( Provider::class, 'save_via_config' ), 10, 2 ); } if ( $next !== null ) { return $next( $changes ); } } /** * {@inheritdoc} */ public function is_responsible_for( string $option_key ): bool { // All keys should be stored in the options table. return true; } /** * Fetches & caches the config from the options table. * * @return array */ private function fetch(): array { if ( ! function_exists( 'get_option' ) ) { return []; } if ( $this->config === null ) { $this->config = (array) get_option( Config::OPTION_KEY, [] ); } return $this->config; } }