How to build custom Sage Roots modern environments extensions utilizing modern Filesystem API schemas
Leveraging the Filesystem API for Custom Sage Roots Environments
Sage Roots, a popular WordPress starter theme, provides a robust foundation for modern theme development. While its core functionality is extensive, developers often encounter scenarios requiring custom environment configurations beyond the default `development`, `staging`, and `production` setups. This typically involves managing distinct sets of configurations for plugins, theme options, or even specific API keys based on the deployment environment. Traditionally, this might involve complex conditional logic within theme files or separate configuration files managed manually. However, by leveraging Sage’s built-in filesystem utilities and a structured approach, we can create highly maintainable and extensible custom environment configurations.
Defining Custom Environment Schemas
Sage Roots utilizes the `wp-config.php` file to define its environments. The `WP_ENV` constant is the primary driver. We can extend this by introducing custom constants or by creating a more structured configuration system. A common pattern is to use a dedicated configuration file that maps environment names to specific settings. For this example, we’ll assume a JSON structure stored within the theme’s `config` directory.
First, let’s define a custom environment, say `local-dev`, which might have different database credentials or API endpoints than the standard `development` environment. We’ll create a file named `config/environments/local-dev.json` within your Sage theme’s root directory.
Example `config/environments/local-dev.json`
{
"wp_config": {
"DB_HOST": "localhost",
"DB_NAME": "my_local_db",
"DB_USER": "root",
"DB_PASSWORD": "password",
"WP_HOME": "http://my-local-dev.test",
"WP_SITEURL": "http://my-local-dev.test"
},
"theme_options": {
"api_endpoint": "https://api.dev.example.com",
"debug_mode": true
},
"plugins": {
"advanced-custom-fields-pro/acf.php": {
"enabled": true,
"settings": {
"google_api_key": "DEV_GOOGLE_API_KEY_12345"
}
},
"woocommerce/woocommerce.php": {
"enabled": false
}
}
}
Integrating Custom Environments with Sage Roots
Sage Roots’ build process, managed by Bedrock, typically handles environment configuration. We need to hook into this process to load our custom environment’s settings. The `config/application.php` file is the central point for this integration.
Modifying `config/application.php`
We’ll extend the existing logic to detect our custom `WP_ENV` and load the corresponding JSON configuration. Sage’s `Config` class provides a convenient way to load and merge configurations.
<?php
/**
* Sage configuration file.
*
* @package Sage
*/
use Roots\Sage\Config;
use Illuminate\Support\Arr;
/**
* @param string $file
* @return string
*/
function config_path($file = '') {
$path = WP_CONTENT_DIR . '/themes/' . basename(get_template_directory()) . '/config';
return $path . ($file ? DIRECTORY_SEPARATOR . $file : '');
}
// Determine the current environment
$env = env('WP_ENV') ?: 'production';
// Base configuration
$config = Config::read('config.php'); // Loads config/config.php
// Load environment-specific configuration
$env_config_path = config_path("environments/{$env}.json");
if (file_exists($env_config_path)) {
$env_settings = json_decode(file_get_contents($env_config_path), true);
if (json_last_error() === JSON_ERROR_NONE) {
// Merge environment settings into the main config
// This is a simplified merge; for deep merging, consider a library
$config = array_replace_recursive($config, $env_settings);
} else {
// Handle JSON parsing error if necessary
error_log("JSON parsing error in {$env_config_path}: " . json_last_error_msg());
}
}
// Apply configurations
Config::apply($config);
// Define constants from wp_config section if present
if (isset($config['wp_config']) && is_array($config['wp_config'])) {
foreach ($config['wp_config'] as $key => $value) {
if (!defined($key)) {
define($key, $value);
}
}
}
// Example of using theme_options
if (isset($config['theme_options']) && is_array($config['theme_options'])) {
// You might store these in theme options or use them directly
// For demonstration, let's assume they are accessible via Config::get()
// Example: Config::get('theme_options.api_endpoint')
}
// Example of managing plugin enablement/settings
if (isset($config['plugins']) && is_array($config['plugins'])) {
add_action('plugins_loaded', function() use ($config) {
foreach ($config['plugins'] as $plugin_file => $plugin_config) {
if (isset($plugin_config['enabled']) && $plugin_config['enabled']) {
// Ensure plugin is active
if (!is_plugin_active($plugin_file)) {
activate_plugin($plugin_file);
}
// Further logic to apply plugin-specific settings if needed
// e.g., if ($plugin_file === 'advanced-custom-fields-pro/acf.php' && isset($plugin_config['settings']['google_api_key'])) {
// // Logic to set ACF Google API Key, potentially via ACF's API or options
// }
} else {
// Ensure plugin is inactive
if (is_plugin_active($plugin_file)) {
deactivate_plugin($plugin_file);
}
}
}
});
}
return $config;
?>
Setting the `WP_ENV` Constant
To activate our custom environment, we need to set the `WP_ENV` constant. This is typically done in `wp-config.php`. For local development, you might set this directly or use environment variables managed by tools like Docker or Vagrant.
Modifying `wp-config.php`
<?php
/**
* Sage configuration.
*
* @package Sage
*/
define('WP_ENV', getenv('WP_ENV') ?: 'development'); // Default to 'development' if not set
// ... other wp-config.php settings ...
// Load Sage configuration
require_once(get_template_directory() . '/config/application.php');
// ... rest of wp-config.php ...
?>
If you are using a local development environment manager (like Local by Flywheel, DevKinsta, or a custom Docker setup), you’ll configure the `WP_ENV` variable within that manager’s settings or via environment files (e.g., `.env`). For instance, if you’re using a `.env` file with a tool that loads it (like WP-CLI’s `wp-env` or a custom script), you would add:
WP_ENV=local-dev
Advanced Considerations and Best Practices
Deep Merging Configuration
The `array_replace_recursive` function is a basic way to merge configurations. For more complex nested arrays, especially where you might want to append rather than replace, consider using a dedicated array merging library or implementing a more sophisticated recursive merge function. This is crucial if your environment configurations have deeply nested structures that need to be combined intelligently.
Plugin Management Logic
The example shows basic activation/deactivation. For more granular control, you might need to interact with plugin APIs directly. For instance, setting ACF field group visibility or modifying WooCommerce settings based on the environment. This often involves checking the environment within plugin-specific hooks or functions and applying settings accordingly.
Security and Sensitive Data
Never commit sensitive data (API keys, database passwords) directly into your version control system, even in JSON files. Use environment variables for secrets. Your `config/environments/*.json` files should reference environment variables, and your `wp-config.php` or `config/application.php` should load these variables using `getenv()` or a library like `vlucas/phpdotenv`.
// Example in config/environments/local-dev.json
{
"wp_config": {
"DB_PASSWORD": "${DB_PASSWORD_LOCAL}" // Expects DB_PASSWORD_LOCAL env var
},
"theme_options": {
"api_endpoint": "${API_ENDPOINT_DEV}",
"google_api_key": "${GOOGLE_API_KEY_DEV}"
}
}
// Example in config/application.php to load env vars
// ... inside the loop for wp_config ...
if (!defined($key)) {
$value_to_define = $value;
if (is_string($value) && strpos($value, '${') === 0 && strpos($value, '}') === strlen($value) - 1) {
$env_var_name = substr($value, 2, -1); // Extract variable name
$env_value = getenv($env_var_name);
if ($env_value !== false) {
$value_to_define = $env_value;
} else {
// Handle missing environment variable, e.g., throw an error or use a default
error_log("Environment variable {$env_var_name} not set for {$key}.");
// Optionally: $value_to_define = 'default_fallback_value';
}
}
define($key, $value_to_define);
}
// ...
Extending Beyond `wp_config`
The `theme_options` and `plugins` sections in the JSON are illustrative. You can define any structure needed for your project. This could include settings for specific plugins, custom post type configurations, or even feature flags that are toggled per environment. The key is to parse this data within `config/application.php` and apply it appropriately, either by directly using `Config::get()` or by hooking into WordPress actions and filters.
Conclusion
By defining custom environment schemas using JSON files and integrating them into Sage Roots’ `config/application.php`, developers gain a powerful and organized way to manage environment-specific configurations. This approach enhances maintainability, reduces the risk of configuration errors, and allows for more sophisticated deployment strategies. Always prioritize security by using environment variables for sensitive data, ensuring your custom environment setup is both robust and secure.