3
\$\begingroup\$

I've stored a set of theme related configuration in a config file.

config/theme.php

// ...

$component = [
  'componentA' = [
    'size' => 'componentDefaultSize',
    'color' => [
      'default' => 'componentDefaultColor',
      'success' => 'componentSuccessColor',
    ]
    'background' => [
      'light' => 'componentLightBackground',
      'dark' => 'componentDarkBackground',
    ]
  ]
];

return [
  // ...
  'component' => $component,
];

This helper function below retrieve related values should the parameters match with keys from theme.php. The parameters are as below

string $type // Type of theme.
string $component // Name of component.
array $properties // Optional properties key name. If not defined, retrieve first array value.
function getTheme(string $type, string $component, array $properties = null)
{
  $theme = null;
  $styles = config("theme.$type.$component");

  foreach ($styles as $style) {
    if (is_array($style)) {

      // If $properties exist retrieve match key value, else retrieve first value of array
      if (isset($properties)) {
        foreach ($style as $key => $class ) {
          foreach ($properties as $property) {
            if ($key == $property) {
              $theme .= $class.' ';
            }
          }
        }
      } else {
        $theme .= $style[0].' '; 
      }
    } else {
      $theme .= $style.' ';
    }
  }
  return $theme;
}

I'm trying to reduce the deep nesting for helper function above but I'm not sure how to achieve that.

Example usage of the helper:

// Without properties
$theme = getTheme('component', 'componentA'); // Return all values and first value of an array

// With properties
$color = 'success';
$theme = getTheme('component', 'componentA', [$color]);

// With multiple properties
$color = 'success';
$background = 'dark';
$theme = getTheme('component', 'componentA', [$color, $background]);

Assistance is much appreciated.

Edited:

1st Attempt:

Toby's comment gave me an idea.

function getTheme(string $type, string $component, array $keys = null)
{
  $theme = null;
  $properties = config("theme.$type.$component");
  foreach ($properties as $property) {
    if (is_array($property) && isset($keys)) {
      if(array_intersect(array_keys($property), $keys)){
        foreach($keys as $key){
          $theme .= $property[$key].' ';
        }
      } else {
        $theme .= reset($property).' ';
      }
    } else if (is_array($property)) {
      $theme .= reset($property).' ';
    } else {
      $theme .= $property.' ';
    }
  }
  return $theme;
}

I switched $properties to $keys for better readability. I still don't like $theme .= reset($property).' '; is called twice.

2nd attempt:

I've managed to refactor it to as below:

function getTheme(string $type, string $component, array $keys = null)
{
  $theme = null;
  $properties = config("theme.$type.$component");
  foreach ($properties as $property) {
    if (is_array($property) && isset($keys)) {
      $theme .= implode(' ', array_values(array_intersect_key($property, array_fill_keys($keys, '')))).' ';
    } else if (is_array($property)) {
      $theme .= reset($property).' ';
    } else {
      $theme .= $property.' ';
    }
  }

  return $theme;
}

Works fine for now but $theme .= implode(' ', array_values(array_intersect_key($property, array_fill_keys($keys, '')))).' '; is a bit of a stretch.

\$\endgroup\$
0

1 Answer 1

4
\$\begingroup\$

I'll review your 2nd and most recent refactor.

  1. Your function getTheme() builds a space-delimited string of class values. Your current function name is clear about the intent, but perhaps a little unclear on the data that it returns. You might consider a more descriptive name or declaring the return type. On the topic of the return type, your function is returning a nullable string. Is there a benefit to returning null? Would you be just as happy to return an empty string? I prefer to reduce the total number of returnable data types so that dependent scripts don't need to juggle as much.

  2. I don't ever use nullable array types. An empty array is falsey and I rely on that fact to make functionless conditional checks which makes nullability unnecessary.

  3. Because you are using concatenation and appending a space after every class name, you will have a result string with a dangling space. This means your string will either not be perfectly clean, or a trim() call will be necessary. I would, instead, use a temporary array, then implode before returning the value.

  4. When writing your looped condition block, handle the easier, negative outcomes first. Processing the non-array $property in the first branch means that you won't need to check the data type again in subsequent branches.

  5. As mentioned earlier, you can use !$keys to determine if an array is empty.

  6. To mutate the $keys values to keys, you can just flip the array instead of assigning values which will never be used. Do this only once before looping the properties.

  7. Use a variadic push array_merge() to merge your filtered associative array of classnames into the temporary array.

  8. Oh, and elseif is one word in PHP.

Code:

function getTheme(
    string $type,
    string $component,
    array $keys = []
): string
{
    $classNames = [];
    $properties = config("theme.$type.$component");
    $whitelist = array_flip($keys);
    foreach ($properties as $property)) {
        if (!is_array($property) {
            $classNames[] = $property;
        } elseif (!$keys) {
            $classNames[] = reset($property);
        } else {
            $classNames = array_merge(
                $classNames,
                array_intersect_key($property, $whitelist)
            );
        }
    } 
    return implode(' ', $classNames);    
}
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.