Vars in CSS

How to Add Variables to Your CSS Files

Tutorial Details
  • Technology: PHP, CSS
  • Difficulty: Intermediate
  • Completion Time: 1-2 hours

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?

Add Comment

Discussion 136 Comments

Comment Page 4 of 4 1 2 3 4
  1. Rob says:

    As a Newbie, I am always searching online for articles that can help me. Thank you
    Wow! Thank you! I always wanted to write in my site something like that. Can I take part of your
    post to my blog?

  2. Maxsim says:

    my God, i thought you were going to chip in with some decisive insght at the end there, not leave it with ‘we leave it to you to decide

  3. this is the solution i have been looking for, i hope. i have a color chooser with 32 colors and when the user clicks on a color the background-color of a div element should change to the selected color(hex). how would i procede to pass this value to the conversion code?

  4. shaffy says:

    nice article

  5. Eddy says:

    Mmmm interesting indeed, I was in serious need of passing variables into CSS and was thinking of how to go about it, this post has helped heaps, Many Thanks.
    EddyF.

  6. adjouromous says:

    www chicks with dicks read with pleasure Accidentally saw. Not expect. Cool You can still search in Google .. fantastic! … I like your posts Who is mulberry? All true, but how else? Yes, the news went on inetu and distributed by starshnoy force

  7. Nazmul says:

    A really helpful script indeed. So many thanks.

  8. Another a informative great writeup by the author looking forward to read more very soon.

  9. Angel Esguerra says:

    Hi,

    Good tutorial, but I think there is an easier way to go about this:

    make your css file into a php script by having two files:

    file 1: base.css
    file 2: base.css.php

    in file 1, create your css as you normally would, but with php variables like so:
    color:;

    in file 2, set all your php variables, include file 1(base.css), then send a header to make it a css file:
    $color = ‘#ffcc00;
    include (‘base.css’);
    header(“Content-type: text/css”);

    in your html, use base.css.php as your stylesheet:

    for caching, you can set .htaccess directives to cache .css.php files

    as an added benefit, you can pass url variables to your base.css.php for theming purposes – you can have something like base.css.php?theme=blue that shows different values to soemthing like base.css.php?theme=orange

    hope this helps!

  10. Very tricky but nice to know. I may ask my programmer to work on this solution for sure.

    Thanks!

  11. abdullah says:

    thanks for info ,great articles for me

  12. daniel says:

    This is a very complicated solution for a simple problem. You can have many CSS files to override the main file with the colors or whatever you want. So why creating classes and redirecting CSS files and do all those things? make no sense. Don’t forget K.I.S.S.

    Cheers
    Daniel

  13. Rob says:

    Hi Jay,
    Excellent tutorial, beautiful layout and very well explained.

    I wanted to include some embedded PHP code in the CCS script, so to get around this I added some additional code in “parse” function within the class.
    This pulls in some additional script to set some constants that are defined in the CCS:

    colour settings:

    @define(“COL_menubar_1_bg”,”#8c0821″);

    menubar.css:
    .menu_bar1{
    background-color: ;
    }

    enhanced_css.php:


    private function parse() {
    // open the css file and throw every line to
    // findAndReplaceVars method
    if (!$content = $this->cache()) {
    $lines = file($this->cssFile);
    foreach($lines as $line) {
    $content .= $this->findAndReplaceVars($line);
    }
    // parse the script though PHP >>>
    define(“APPPATH”,”../../app”);
    require_once APPPATH . ‘/scripts/config.php’;
    require_once APPPATH . ‘/scripts/colour_settings.php’;
    $eval_result = eval (‘ob_start (); ?>’.$content.’<?php $content=ob_get_clean();');
    // end of PHP parse <<<
    }
    return $content;
    }

  14. Thought it was really great!

    Browser independent solution even if this is brought into CSS.

    I altered a line in:

    private function parse()


    $lines = array_merge( file(“common_css_vars.txt”), file($this->cssFile) );

    and use a same directory file “common_css_vars.txt” to store commonly used $vars for substitution.

    E.g. I moved:

    $font: arial, sans-serif;

    Into “common_css_vars.txt” to test it.

    Paul

  15. dinusuresh says:

    I just spotted a small mistake, in step 2 at the end while mentioning setting for web hosts that don’t allow override, the replace link

    you left out the extension for the enhanced_css file.

  16. Meh says:

    Thank you for an interesting tutorial. In all honesty though, what it has done is made me realise that in CSS, classes themselves are the variable. Considering the code

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

    what has been made here is an overcomplicated solution to an easy problem. I’m good at that too ;) but a seemingly better way of handling this is by writing

    .font-arial {
    font-family: arial, sans-serif;
    }

    .font-200 {
    font-size: 200%;
    }

    .main-color {
    color: #3D7169;
    }

    .background-black {
    background-color: #000;
    }

    p {
    padding: 10px;
    }

    Of course, you’ll be using a lot more class selectors in your HTML, but it will be much easier to read because of it (each tag will have a description of the style applied to it in its class variable, so less hunting through CSS files or inspecting tags to generally work out whats happening. If you’re fixing a bug you have to look, but if you just wanted to know the thought process behind the design, its much more descriptive) If all your paragraphs are to be padded by 10px, then there is no need to make a .p-10 class, but if you have different paddings for different p’s, its pretty obvious which direction to go :) It is generally good style to keep each selector as minimal as possible, which is also what the above does in the long term.

Comment Page 3 of 3 1 2 3

Add a Comment

To add a code snippet to your comment, please wrap your code like so: <pre name="code" class="html">YOUR CODE</pre>. You can replace the class name with "js," "css," "sql," or "php." If there are any "<" or ">" within your code, please search and replace them with: &lt; and &gt; respectively.