Is there a better, more efficient way - considered a "best practice" - to approach this?
Yes! Storing structured data in a single option is bad practice. You should use the option name to seperate your data.
Otherwise by using arrays and objects, WordPress cannot store those in the database, so it has to turn it into a string, meaning:
- Clever users can insert serialised data, which gets turned into real arrays and objects when the option is retrieved on the next page load. This is called an object deserialisation attack, and has been known for a while
- Debugging tools will show you serialised PHP rather than clean values
- SQL queries that modify these values will mangle them, PHP serialised data contains the string length. So a naive site migration could break the site
- It's impossible to use the
options.php UI or the network admin UI to update the option values
- By smushing all the options into a single option, you could run into the upper limit on option size
- You've lost all control over which options are auto-loaded. Because they're all stored as a single option, either all of them are auto-loaded or none of them.
- A lot of the checks and balances that WP and the Database would have done for you now need to be reproduced in PHP code, such as checking array keys exist, checking for empty arrays, etc
But most importantly, it's simply unnecessary to store them in an array, and it gives you no benefits.
So instead, use the option name to namespace your values, not arrays. e.g.
my_option_depth_mor_depth. You could even use slashes similar to folder paths. Some popular plugins take this approach with action and filter names, e.g. ACF
I mean, the more functions you use, the slower the code compiles, especially in my case where the my_options() function gets called quite a lot.
To see this kind of slowdown you would need to define a lot of functions. Your single function will make your site slower by several microseconds. This will be imperceptible, and is statistically insignificant.
It's what your function does, not how many functions that matters.
However, I believe you're under the false assumption that every call to
get_option makes a fresh call to the database. This is not the case.
- At the beginning of a request, WP fetches every option marked as autoload in a single query
- When options are loaded, they're stored in
WP_Cache, along with any posts and their meta. A second call to
get_option doesn't even touch the database.
get_option repeatedly is cheap. If that is all your function does then you have nothing to fear. Don't speculate about these things, measure them.
Some additional notes:
- The function calls
get_option then immediatley uses the
 operator. It never checks that those array keys exist, or that the option even holds an array. As a result, this will crash the first time it's used if the option isn't set in advance
- There's no validation on
$key to check that such a key actually exists
- There's no validation that
$key is actually a string, it might be an array, an object,
null, etc The parameter should have a type hint, e.g.
( string $key )
- Use modern array syntax in line with the WP coding standards, e.g.
[ 1,2,3,4 ] rather than
array( 1,2,3,4 )
- Run your code through PHPCodesniffer with the WPCS ruleset for the official WP coding standards. It will flag security and performance issues, including the storage of serialised data in the database.
Overall, the approach you tried to take with the function is of negligible performance impact. Trying to make it faster would be a micro-optimisation, no worthy of your time. There are very likely big performance gains to be had elsewhere. Such as avoiding DB writes on the frontend, eliminating HTTP requests, avoiding searching for posts via their post meta values, etc
A Note on Theme Mods
You mentioned your options are for theme settings, which means you're not using the correct API, you should be using theme mods instead.
get_theme_mod instead of
get_option. WP will automatically namespace the values and keys to your theme so that even if another theme has the same names it won't matter. This is the API you should be using, ideally with the customizer.