Skip to content

Options

Miguel Muscat edited this page Nov 30, 2022 · 1 revision

Options refer to the database records in the WordPress wp_options table, typically used to store settings and other persistent information.

Creating Options

The SDK provides a wrapper class for options to simplify the reading and writing of values, with proper type handling.

use RebelCode\WpSdk\Wp\Option;

new Option('my_option', Option::default() true, false);

The constructor accepts the following arguments:

  • The name of the option.
  • The type of the option.
  • An optional default value.
  • Whether the option is autoloaded.

Creating Transients

Transients have a similar API to options, and can be created using a similar constructor signature:

use RebelCode\WpSdk\Wp\Transient;

new Transient('my_transient', Transient::default(), 300, 'default');

The constructor accepts the following arguments:

  • The name of the option.
  • The type of the option.
  • An optional expiry time, in seconds.
  • An optional default value.

Using the base class

Both options and transients extend the RebelCode\WpSdk\Wp\AbstractOption class. If your code supports both, you can use this abstract class as a type hint to accept either.

use RebelCode\WpSdk\Wp\AbstractOption;

function my_code(AbstractOption $option) {
    $option->getValue();
    $option->setValue('value');
    $option->delete();
}

Both options and transients inherit most of their functionality from this class. The remainder of this document will focus mainly on options to avoid duplication, but keep in mind that transients also support most of what is covered in this document.

Option Types

WordPress options are always stored as strings in the database; scalar values are cast into strings, while arrays and objects are serialized using the PHP serialization format.

The SDK provides a simple but powerful type system for options, which allows for the automatic conversion of option values to and from strings and their desired native PHP types.

The SDK provides the following types:

  • Option::default() - The default type, which does not perform any conversion.
  • Option::string() - Reads options without conversion, but ensures that written values are cast to strings.
  • Option::bool() - Reads options as booleans, and writes them as 1 or 0 strings.
  • Option::int() - Reads options as integers, and writes them as numeric integer strings.
  • Option::float - Reads options as floats, and writes them as numeric float strings.
  • Option::jsonArray() - Decodes option JSON strings as arrays, and encodes arrays into JSON strings.
  • Option::jsonObject() - Decodes option JSON strings as objects, and encodes objects into JSON strings.

Usage

Options can be queried or manipulated using the methods in the Option instance itself.

Example 1: Read a value

use RebelCode\WpSdk\Wp\Option;

$option = new Option('count', Option::int(), 0, false);
$count = $option->get();

Example 2: Write a value

$option->set(5);
$option->get(); // 5

Example 3: Delete a value

$option->delete();
$options->get(); // 0

Note that the return type of the get() method depends on the option type. In the above example, the option type is Option::int(), which means that the get() method will return a PHP integer value.

The Option class uses the @template PHPDoc tag. If you IDE supports it, you can type hint your Option instances to get proper type hinting for the getValue() and setValue() methods.

Example 4: Type hinting an option:

use RebelCode\WpSdk\Wp\Option;

/** @var Option<bool> $option */
$option = new Option(Option::BOOL, 'my_flag', false);
$value = $option->getValue();

⚠️ Important:
The Options API is primarily designed such that the values passed to the set() method are values of the appropriate type, and that the values that are stored in the database are written in the appropriate format. Unexpected values may yield unexpected results. For instance:

Example 5: Invalid write value

use RebelCode\WpSdk\Wp\Option;

$option = new Option('my_option', Option::int(), 0, false);
$option->set(['a', 'b', 'c']); // This will write a "0" string to the database

Example 6: Unexpected database value

use RebelCode\WpSdk\Wp\Option;

// |-------------------|-----------------------|
// | Option            | Value                 |
// |-------------------|-----------------------|
// | my_option         | a:1:{i:0;s:4:"test";} |
// |-------------------|-----------------------|

$option = new Option('my_option', Option::int(), 0, false);
$option->get(); // 0

It is recommended not to always use the appropriate types, to avoid unexpected results.

Option Sets

Sometimes, you may need to access or modify multiple options at once. In such situations, it would be inconvenient to have to pass all the necessary Option instances. It would be much simpler if you could represent your plugin's settings using a single instance.

Option sets provide this functionality. They are simply a collection of Option instances. You can use a single set for all of your options. Alternatively, you can create multiple sets for different groups of options.

use RebelCode\WpSdk\Wp\Option;
use RebelCode\WpSdk\Wp\OptionSet;

$greeting = new OptionSet([
    'enabled' => new Option('show_greeting', Option::bool(), true),
    'text' => new Option('greeting_text', Option::string(), ''),
]);

// Get a single option value
$showTutorial = $greeting->get('enabled'); // => true

// Set a single option value
$greeting->set('text', 'Welcome to my website!');

// Set multiple option values
$greeting->update([
    'enabled' => false,
    'greeting' => 'Welcome to my blog!',
]);

// Delete an option
$greeting->delete('enabled');

Note 1: When creating an OptionSet, you must pass an associative array. The keys are used to identify the options, and do not need to necessarily be the same as the option names. In the above example, the greeting_text option uses the text key in the set, allowing us to use a more readable key.

Note 2: You can also use Transient instances in option sets.

Custom Types

You can create your own custom types by implementing the OptionType interface. This interface has two methods that need to be implemented; one for parsing values read from the database, and another to serialize values before they are written to the database.

For instance, here's a custom type that writes a WordPress post ID:

use RebelCode\WpSdk\Wp\Option;
use RebelCode\WpSdk\Wp\OptionType;

class MyType implements OptionType
{
    public function parse($value)
    {
        $int = Option::int()->parse($value);
        
        return get_post($int);
    }

    public function serialize($value)
    {
        if ($value instanceof \WP_Post) {
            // If a WordPress post, write its ID
            return (string) $value->ID;
        } else {
            // Fallback to writing an integer
            return Option::int()->parseValue($value)
        }
    }
}

You can then use your custom type simply by instantiating it and passing it to the Option constructor:

use RebelCode\WpSdk\Wp\Option;

$option = new Option('checkout_page', new MyType(), null, false);

$post = get_post(5);
$option->setValue($post);

$option->getValue(); // => WP_Post instance

If the type is reused multiple times, you may want to create a singleton instance for the type, to avoid unnecessary memory usage. You can also declare a service for the type. The next section explains this in more detail.

Services

Factories can be easily created for Option, Transient, and OptionSet instances:

Example 1: Creating factories

use RebelCode\WpSdk\Module;
use RebelCode\WpSdk\Wp\Option;
use RebelCode\WpSdk\Wp\OptionSet;
use RebelCode\WpSdk\Wp\Transient;

class MyModule extends Module
{
    public function getFactories() : array
    {
        return [
            'options/greeting' => Option::factory('greeting_text', Option::string(), 'Hello, world!'),
            'options/show_tutorial' => Transient::factory('show_tutorial', Transient::bool(), 3600),
            
            'options' => OptionSet::factory([
                'greeting' => 'options/greeting',
                'show_tutorial' => 'options/show_tutorial',
            ]),
        ];
    }
}
  • Note 1: The Option::factory() method accepts similar arguments as the Option constructor.
  • Note 2: The Transient::factory() method accepts similar arguments as the Transient constructor.
  • Note 3: The Option::factory() and Transient::factory() methods can be given a service ID as their type, instead of a type instance. This is useful when you want to use a custom option type that is declared as a service.
  • Note 4: The OptionSet::factory() method accepts an array of option service IDs, not instances.

Example 2: Using a service ID for custom types:

use RebelCode\WpSdk\Module;
use RebelCode\WpSdk\Wp\Option;
use Dhii\Services\Factories\Constructor;

class MyModule extends Module
{
    public function getFactories() : array
    {
        return [
            // Your custom type's service
            'custom_type' => new Constructor(CustomType::class, []),

            // An option that uses the custom type
            'my_option' => Option::factory('my_option', 'custom_type', 'Hello, world!'),
        ];
    }
}

Clone this wiki locally