Advertisement
HTML & CSS

How to Add Variables to Your CSS Files

by

Let's try something different on nettuts+ today. Any designer who has worked with large CSS files will agree that its major weakness is its inability to use variables. In this article, we will learn how to implement variables by using PHP and Apache's URL rewrite mod.


Preface

This technique is somewhat simple. We will ask Apache to redirect any stylesheet to a specific PHP script. This script will open the stylesheet, and read it line by line to find and replace any user-defined variables. Finally the parsed content will be displayed as pure CSS; browsers won't notice the difference. To close this tutorial, we will also see how to cache the processed result to avoid unnecessary CPU usage.

Please note that some basic PHP (OOP), Apache and HTTP knowledge are expected.

Requirements:

  • Apache with Rewrite mod on
  • PHP 5

Step 1 - Build the Project

Let's first create our simple project structure. Add an index.html file to the root of your project.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Variables in CSS Files... It's possible!</title>
    <link href="css/styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <h1>Variables in Stylesheets</h1>
    <p>It's possible!</p>
    <p>With PHP and Apache URL Rewrite Mod</p>
</body>
</html>

Now, create a CSS file with the following variables and place it in a "css" folder

$font: arial, sans-serif;
$main-color: #3D7169; $secondary-color: #000;

h1 {
    font: 200% $font;
    color: $main-color;
}
p { 
    background: $secondary-color;
    color: $main-color;
    font-family: $font;
    padding: 10px;
}

Finally, create a blank PHP file named enhanced_css.php and a blank .htaccess file. This latter file overrides the default configuration of the server and is applied to the folder and its subfolders.

Now our project should look like this:


Step 2 - Redirect CSS files to a PHP Script

We want to redirect any URL with a CSS extension to our PHP script. The Apache server allows us to do this by utilizing URL Rewrite mod.
First, be sure that the "rewrite_module" modules is active on your server. Go and find the httpd.conf file in your Apache folder. Edit it and search for this line:

LoadModule rewrite_module modules/mod_rewrite.so

If needed, uncomment it by removing the prepended “#”, and restart Apache to make sure that your configuration settings are active.

Now, edit your .htaccess file and add the following lines.

RewriteEngine on
RewriteRule ^(.*\.css)$ enhanced_css.php?css=$0

Save it. As previously mentioned, the lines above ask Apache to catch all of the URLs with the .css extension and redirect them to "enhanced_css.php". The original CSS file path is passed in as a 'css' parameter.

For instance :

/css/styles.css 

Will be redirected to:

enhanced_css.php?css=/css/styles.css

Note:

Some hosting solutions don't allow their settings to be overrided by the user's ones. If so, the links to the stylesheets in the HTML code must be replaced manually.

In such instances, you will have to replace:

<link href="css/styles.css" rel="stylesheet" type="text/css" />

with:

<link href="enhanced_css?css=css/styles.css" rel="stylesheet" type="text/css" />

Step 3 - Parse the CSS file with PHP

Since the CSS files are redirected our PHP script, let's build a class named "Enhancedcss" to read them, find and replace variables, then display the contents as pure CSS. Our class will be instantiated by passing $_GET['css'] to the constructor. Remember the .htaccess redirection. $_GET contains the path to the current stylesheet.

if (isset($_GET['css'])) {
    $css = new EnhancedCss($_GET['css']);    
    $css->display();
}

The basic implementation of the class is composed of four methods. Later, we will add a caching method.

class EnhancedCss {
    public $values;
    public $cssFile;
    
    public function __construct($cssFile) {
        // check if the css file exists
    }
    
    private function parse() {
        // open the css file and throw every line to 
        // findAndReplaceVars method
    }
    
    private function findAndReplaceVars($line) {
        // find the variable definitions, store the values,
        // replace the variable by their defined values.
    }
    
    public function display() {
        // display the new parsed content        
    }
}

The Constructor

Nothing sexy here. We check if the requested CSS file exists. If not, the script returns a 404 http error. The path of the CSS file is kept in the $this->cssFile property to compute the name of the cache file later.

public function __construct($cssFile) {
    if (!file_exists($cssFile)) {
        header('HTTP/1.0 404 Not Found');
        exit;
    }
    $this->cssFile = $cssFile;
}

The Parse Method

This method opens the CSS file and reads it line by line.

private function parse() {
    $content = '';
    $lines = file($this->cssFile);
    foreach($lines as $line) { 
        $content .= $this->findAndReplaceVars($line); 
    }
    return $content;
}

The file function is used here. It can be useful because it opens a file and returns the content as an array of lines. Each line is thrown to the findAndReplaceVars which processes the variables. The parsed content is then returned.

The FindAndReplace Method

This method is the primary workhorse of our class. It finds the variable definitions, stores theirs values in an array. When a variable is found, and if its value exists, it is replaced by the value.

private function findAndReplaceVars($line) {
    preg_match_all('/\s*\\$([A-Za-z1-9_\-]+)(\s*:\s*(.*?);)?\s*/', $line, $vars); 
    $found     = $vars[0];
    $varNames  = $vars[1];
    $varValues = $vars[3];
    $count     = count($found);    
                    
    for($i = 0; $i < $count; $i++) {
        $varName  = trim($varNames[$i]);
        $varValue = trim($varValues[$i]);            
        if ($varValue) {
            $this->values[$varName] = $this->findAndReplaceVars($varValue);
        } else if (isset($this->values[$varName])) { 
            $line = preg_replace('/\\$'.$varName.'(\W|\z)/', $this->values[$varName].'\\1', $line);
        }
    }
    $line = str_replace($found, '', $line);
    return $line;
}

Lots of code here. Let's review it in detail.

private function findAndReplaceVars($line) {
    preg_match_all('/\s*\\$([A-Za-z1-9_\-]+)(\s*:\s*(.*?);)?\s*/', $line, $vars);

Here, we apply a regular expression to the current line. This expression matches and extract patterns like $variable:$value; (and some variants) or $variable in the current line. I wont go further here. Regular expressions are a complex topic which deserve an tutorial of their own. The Preg_match_all function returns all matches.

For example, the third line of our project's CSS file -

$main-color: #3D7169; $secondary-color: #000;

- will return this array:

$vars => Array 
(
    [0] => Array
        (
            [0] => $main-color: #3D7169;
            [1] => $secondary-color: #000;
        )

    [1] => Array
        (
            [0] => main-color
            [1] => secondary-color
        )

    [2] => Array
        (
            [0] =>  : #3D7169;
            [1] =>  : #000;
        )

    [3] => Array
        (
            [0] =>  #3D7169
            [1] =>  #000
        )
)

We assume that $vars[0] contains the complete match, $vars[1] contains the names of the variables, and $vars[3] contains the values. Let's organize the array to keep it clearer.

$found     = $vars[0];
$varNames  = $vars[1];
$varValues = $vars[3];

Now it's crystal.

$found => Array
        (
            [0] => $main-color: #3D7169;
            [1] => $secondary-color: #000;
        )
$varNames => Array
        (            
            [0] => main-color
            [1] => secondary-color
        )
 $varValues => Array
       (
           [0] =>  #3D7169
           [1] =>  #000
       )

We count how many variables have been found in the current line.

    $count = count($found);

This way we can cycle through each entry of our variables array. To make things clearer we set some new variables to handle the name and value.

for($i = 0; $i < $count; $i++) {
    $varName  = trim($varNames[$i]);
    $varValue = trim($varValues[$i]);            

    // ...
}

Variable Definitions

If $varValue is not empty, we're facing a variable defintion. So we have to store this value in the $this->values property.

if ($varValue) {
    $this->values[$varName] = $this->findAndReplaceVars($varValue);
} else if ...

Note that we pass the variable value in the findAndReplaceVars method again. This way, other potential variables will be processed as well.
The values are stored in the $this->values array with the name of the variable as the key. At the end of the script, the $this->values array looks like this.

Array
(
    [font] => arial, sans-serif
    [main-color] => #3D7169
    [secondary-color] => #000
)

Variable Applications

If $varValue is empty, we're facing a variable application. We check if this variable exists in the values array. If it does, we replace the variable name by its value.

} else if (isset($this->values[$varName])) { 
    $line = preg_replace('/\\$'.$varName.'(\W|\z)/', $this->values[$varName].'\\1', $line);
}

This replacement might seem to be abnormally complicated. Actually no, this replacement takes care of replacing the $variable only if it is followed by a non character (\W) or an end-of-line (\z).

Finally, we remove the entire match to keep the stylesheet clean and valid. The processed line is returned.

    $line = str_replace($found, '', $line);
    return $line;
}

The Display Method

This method displays the parsed stylesheet. To be served to the browser as CSS content, the header is set to text/css content type.

public function display() {        
    header('Content-type: text/css'); 
    echo $this->parse();
}

Step 4 - Cache the Result

At this point, everything is working perfectly. However, the operation can be very CPU-consuming when used with larger websites.

After all, we don't need to parse the CSS files every time the browser needs it. The process only needs to be run the first time to create the cache, or if the original CSS file has been modified since the last caching operation. Otherwise, the previously rendered result can be reused. So, let's add a caching solution to our script.

Add a new folder named cache to the project. If needed, give this folder the right to be written in by applying a chmod 777. Now our project should look like this:

The Cache Method

A new method has to be added. Its function will be to :

  • read the cache file if it exists.
  • create and store the rendered results.
  • update existing cache file if the CSS file has been modified.

All the logic is handled by the method below:

private function cache($content = false) {
    $cacheFile = "cache/".urlencode($this->cssFile);
    if (file_exists($cacheFile) && filemtime($cacheFile) > filemtime($this->cssFile)) {
        return file_get_contents($cacheFile);
    } else if ($content) {
        file_put_contents($cacheFile, $content);
    }
    return $content;
}

Let's explain this code. The cache file name is computed from the original CSS file name previously kept in the $this->cssFile property. Finally, we use the urlencode function.

$cacheFile = "cache/".urlencode($this->cssFile);

That way a CSS file as

/css/styles.css

will be cached as

/cache/css%2Fstyles.css

We need to check if the cache file already exists, (file_exists) and if so, check if its creation date is not prior to the modification date (filemtime) of the CSS file.

if (file_exists($cacheFile) && filemtime($cacheFile) > filemtime($this->cssFile)) {
    return file_get_contents($cacheFile);

Otherwise, we create/recreate the cache file.

} else if ($content) {
    file_put_contents($cacheFile, $content);
}

Now the rest of the class must deal with this new method. Two methods need to be modified.

private function parse() {
    if (!$content = $this->cache()) {
        $lines = file($this->cssFile);
        foreach($lines as $line) { 
            $content .= $this->findAndReplaceVars($line); 
        }
    }
    return $content;
}

The parsing method now checks the cache before to run the whole process. If there is no cache available, the CSS file is parsed, otherwise the cached content is returned.

public function display() {
    header("Content-type: text/css"); 
    echo $this->cache($this->parse());
}

Finally, the method displays the right content (new or cached) provided by the caching method.

Browser Caching

For security reason (sessions, dynamic contents) browsers don't keep PHP results in their cache. A real CSS file would have been
cached but not the result of our script. We have to deal with the browser to emulate the behavior of a real CSS file. Let's add some lines to the constructor.

public function __construct($cssFile) {
    if (!file_exists($cssFile)) {
        header('HTTP/1.0 404 Not Found');
        exit;
    }

    // Deals with the Browser cache
    $modified = filemtime($cssFile);
    header('Last-Modified: '.gmdate("D, d M Y H:i:s", $modified).' GMT');
	
    if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
        if (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $modified) {
            header('HTTP/1.1 304 Not Modified');
            exit();
        }
    }

    $this->cssFile = $cssFile;
}

We copy the last modification date of the original CSS file to our result in the header.
Basically, headers are exchanged by browsers and servers before serving data.
When the browser has a copy of a page in its cache, it send a HTTP_IF_MODIFIED_SINCE request to the server
with the date previously given by header('Last-Modified', ...) the day it was cached. If the dates match, the content
is up to date and doesn't need to be reloaded; so we send a 304 Not Modified response and exit the script.

A shorter way would be to simply add header('Cache-Control: max-age=3600'); to the file. The content will be cached
1 hour (3600 seconds) by any browser.


Conclusion

Done! You can now check one of your stylesheets on your server. For example, http://localhost/myproject/css/styles.css

$font: arial, sans-serif;
$main-color: #3D7169; $secondary-color: #000;

h1 {
    font: 200% $font;
    color: $main-color;
}
p { 
    background: $secondary-color;
    color: $main-color;
    font-family: $font;
    padding: 10px;
}

Becomes :

h1 {
    font: 200% arial, sans-serif;
    color: #3D7169;
}
p { 
    background: #000;
    color: #3D7169;
    font-family: arial, sans-serif;
    padding: 10px;
}

I hope you enjoyed this tutorial. What are your thoughts?

Related Posts