Dynamic value in Twig or preprocess based on config and getHost() I need to dynamically change a value in Twig templates by selecting a configuration value (defined in a custom module) based on the current environment.
I did the following in a Form class, which works fine:
  /**
   * Generate parent site Base URL per environment.
   *
   * @return string
   *   The parent site Base URL used in all templates site-wide.
   */
  public function getBaseUrl() {
    $host = $this-&gt;requestStack-&gt;getCurrentRequest()-&gt;getHost();
    $config = $this-&gt;configFactory-&gt;get('clientname_cta_base.settings');
    switch ($host) {
      case 'resources.clientname.com':
        // Set $base_url to Production environment.
        $base_url = $config-&gt;get('prod_base_url') ?? [];
        break;
      case 'uatresources.clientname.com':
        // Set $base_url to UAT environment.
        $base_url = $config-&gt;get('uat_base_url') ?? [];
        break;
      case 'qaresources.agencyname.com':
      case 'qaresources.clientname.com':
        // Set $base_url to QA environment.
        $base_url = $config-&gt;get('qa_base_url') ?? [];
        break;
      default:
        // All other environments, including local, link to Dev.
        $base_url = $config-&gt;get('dev_base_url') ?? [];
        break;
    }
    return $base_url;
  }
  /**
   * Generate CTA destination URL per environment.
   *
   * @return string
   *   The CTA destination URL used in all templates site-wide.
   */
  public function getCtaUrl() {
    $config = $this-&gt;configFactory-&gt;get('clientname_cta_base.settings');
    $cta_url = $this-&gt;getBaseUrl() . $config-&gt;get('default_link_dest') ?? [];
    return $cta_url;
  }

This displays the desired URL paths in two disabled fields when the SettingsForm is loaded:
    // Active URL configuration (read-only).
    $form['clientname_cta_base_computed_base_url'] = [
      '#type' =&gt; 'textfield',
      '#title' =&gt; $this-&gt;t('Computed base URL (Preview)'),
      '#default_value' =&gt; $this-&gt;getBaseUrl(),
      '#description' =&gt; $this-&gt;t('Read-only value based on config and environment. Save changes to update.'),
      '#attributes' =&gt; ['disabled' =&gt; 'disabled'],
    ];
    $form['clientname_cta_base_computed_cta_url'] = [
      '#type' =&gt; 'textfield',
      '#title' =&gt; $this-&gt;t('Computed CTA URL (Preview)'),
      '#default_value' =&gt; $this-&gt;getCtaUrl(),
      '#description' =&gt; $this-&gt;t('Read-only value based on config and environment. Save changes to update.'),
      '#attributes' =&gt; ['disabled' =&gt; 'disabled'],
    ];

I could even store these updated values in configuration when the SettingsForm is submitted -- but that will not quite get me what I want.
These values need to be computed dynamically by selecting config based on the value of $host at runtime -- importing the Dev environment's computed config values to the QA environment won't do. So I am looking for a way to call the module's public methods (getBaseUrl() and getCtaUrl()) in either theme preprocess or Twig templates.
Is this approach possible? Should I have used theme settings instead?
In case you wondered, I resolved this by adding my methods to a service class, as @Hudri suggested in comments.
I can call those methods in a Form or other OO class by dependency injection of the Drupal\clientname_cta_base\Cta\Base service class:
    // Active URL configuration (read-only).
    $form['clientname_cta_base_computed_base_url'] = [
      '#type' =&gt; 'textfield',
      '#title' =&gt; $this-&gt;t('Computed base URL (Preview)'),
      '#default_value' =&gt; $this-&gt;ctaBase-&gt;getBaseUrl(),
      '#description' =&gt; $this-&gt;t('Read-only value based on config and environment. Save changes to update.'),
      '#attributes' =&gt; ['disabled' =&gt; 'disabled'],
    ];
    $form['clientname_cta_base_computed_cta_url'] = [
      '#type' =&gt; 'textfield',
      '#title' =&gt; $this-&gt;t('Computed CTA URL (Preview)'),
      '#default_value' =&gt; $this-&gt;ctaBase-&gt;getCtaUrl(),
      '#description' =&gt; $this-&gt;t('Read-only value based on config and environment. Save changes to update.'),
      '#attributes' =&gt; ['disabled' =&gt; 'disabled'],
    ];

The example above does not include all the dependency injection setup in the Form class, or the service class setup, services YAML config, and other steps that are well documented elsewhere.
In theme preprocess, dependency injection is not available, so I get the computed values via \Drupal::service('cta.base') like so:
/**
 * Implements template_preprocess_page().
 */
function clientname_preprocess_page(&amp;$variables) {
  $service = \Drupal::service('cta.base');
  $config = \Drupal::config('clientname_cta_base.settings');
  $variables['computed_base_url'] = $service-&gt;getBaseUrl();
  $variables['computed_cta_url'] = $service-&gt;getCtaUrl();
  $variables['default_link_text'] = $config-&gt;get('default_link_text');
}

Note that for configuration values that are not dynamically generated, I can just access them directly in theme preprocess via \Drupal::config('clientname_cta_base.settings').