views:

32

answers:

4

I'm re-writing a website that will support multiple skins. Currently, I'm just planning on allowing images and CSS to be modified, but the underlying HTML will not change. And if the skin does not modify an image or CSS file, it inherits that file from the base skin.

With that in mind, here are the 3 ways I've considered so far for serving up skin-dependent files:

  1. Wrap all skin-specific requests requests in the view with a lookup function, ie:
    <img src="<?php get_skin_file('/images/header.png', 'skin1'); ?>" />

    function get_skin_file($file, $skin) {
        $skin_file = '/' . $skin . '/' . $file;
        if(is_readable($skin_file)) return $skin_file;
        return '/default/' . $file;
    }
  1. Have php serve the image

    <img src="/header.png.php?skin=skin1" />

  2. Always attempt to load the skin file and if it doesn't exist, use ModRewrite to send the result to a php handler script:

    <img src="/skin1/header.png" />

Number 2 is what I'd like to do, but I'm just concerned with the performance implications of having PHP serve up basically every image file.

My userbase is small enough (about 30k users) that I don't think it'd really be an issue, but I'd also just like to learn what other folks do in this situation.

Thanks.

EDIT: I have no idea why my code is not formatted properly. I hit the code button and checked that it's 4 spaces, but it's still ugly. Sorry about that.

+1  A: 

All three of those are perfectly valid options. I'd personally prefer the first, though using an image name rather than an image path. If your theme control bits are powerful enough, this can allow themes to define their own extra user-controllable images.

Consider a fourth option: don't actually insert images using img tags. Instead, use divs with background images. This can work for the majority of cases, and leaves defining the images entirely up to the stylesheet. Unfortunately this is also a much more complex option.

Charles
+1  A: 

I would try to build this so that it is modular and easily adaptable for future changes. I would avoid putting in hard locations as much as possible in your code, or at least minimize it to 1 location.

One thing that may be worth looking into is having some sort of ini file or an xml file that contains information regarding your pictures for specific skins. That way the hard locations aren't in your php or html, they are just in a resource file that is easily changeable.

I like using xml so here is an example of what you could do.

Here is the html.

<?php $skin = "notDefault"; ?>
<img src="<?php get_skin_image("header",$skin) ?>" />
<img src="<?php get_skin_image("footer",$skin) ?>" />

Then here would be your function, if the skin image isn't found it will pull it from the default xml file.

function get_skin_image($type,$skin){
    $skin_xml_data = xml2array("skinFolderLocation/ " . $type . ".xml"); 
    $default_xml_data = xml2array("defaultFolderLocation/default.xml");
    // here is a link to the xml2array function for you to download 
    // http://www.php.net/manual/en/function.xml-parse.php#87920
    if(isset($skin_xml_data[$skin][$type])){
        return $skin_xml_dat[$skin][$type];
    } else {
        return $default_xml_data["default"][$type];
    }
}

And finally the xml page saved as notDefault.xml

<notDefault>
    <header>locationToHeader/header.png</header>
    <footer>locationToFooter/footer.png</footer>
</notDefault>
stmpy
Thanks for the suggestion. I'd considered this idea, but I'm trying to avoid configuration files as much as possible. If my substitution logic was more complex and/or I didn't have control over the creation of the skins, an XML file like that would make a lot of sense, but since my substitution logic is so simple and I control the skins, checking the file system seems like a better fit for my app.
A: 

I wound up going with a variation of option 2. I'm having apache look for url patterns and if a skin-replaceable url is present, the request is routed to my skin image select script.

Here is the relevant apache config:

<Directory /var/www/website>
  RewriteEngine on
  RewriteBase /

  RewriteCond %{REQUEST_URI} \.select.(png|jpg|gif|txt|css)$ [NC]
  RewriteRule ^(.*)$ index.php?mode=select&q=$1 [L,QSA]
</Directory>

Here is the PHP code from the file that renders the image:

<?php
preg_match("%^(?P<path>.+)\.select\.(?P<ext>(jpg|gif|png|txt|css))$%", $_REQUEST['q'], $matches);

$base = '/' . $matches['path'] . '.' . $matches['ext'];

$file = '/skins/' . App::get('skin') . '/' . $base;

if(is_readable($file)) {
  header('Content-type: image/' . $matches['ext']);
  header('Content-Transfer-Encoding: binary');
  header('Content-Length: ' . filesize($file)+1);
  readfile($file);
  exit();
}
//custom file isn't available, so load the default
$file = '/skins/default/' . $base;
header('Content-type: image/' . $matches['ext']);
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($file)+1);
readfile($file);
exit();
?>

so for any asset in my view that needs to be skin-aware, I load it like:

<img src="/images/header.select.png" />

and apache catches it and sends it to my handler script.

Thanks for your help on that. Both of the other 2 suggestions provided stmpy and Charles would also work, but this was the ideal solution for me. As a bonus of doing it this way, I can also replace images in css and what not dynamically without custom css files. My application will largely consist of just replacing the primary background, so all that will be necessary for my themes, for the most part will just be creating a new skin folder and uploading one background image.

A: 

Could you not namespace your skin's assets? For example, most applications I build have an inc directory (containing programming logic) and a tpl directory, which is then broken down into separate directories for separate skins. If the site only has one skin, then they'll only be one directory.

For example:

inc/
    ...
tpl/
    default/
        assets/
            css/
            images/
            js/
    alternative/
        assets/
            css/
            images/
            js/

In my controller script, index.php, I can listen for a tpl variable to be passed via the $_GET array. For example:

if (!isset($_SESSION['tpl'])) {
    $tpl = "default";
}
if (isset($_GET['tpl']) && is_dir('tpl/'.$_GET['tpl'])) {
    $_SESSION['tpl'] = $_GET['tpl'];
}
$tpl = $_SESSION['tpl'];

Now, the current tpl value is available in my $_SESSION array (I could easily change this to use cookies instead if I wanted the value to persist between sessions) and $tpl variable. I can then get images and CSS paths like this:

<img src="tpl/<?php echo $tpl; ?>/assets/images/heading.jpg" alt="Heading image" />

<link rel="stylesheet" href="tpl/<?php echo $tpl; ?>/assets/css/screen.css" type="text/css" />

Asset (CSS, images, JavaScript) requests could then be wrapped in a custom function. This could also handle cases where say, all your JavaScript files were contained in the default skin, and you didn't want to duplicate them in your alternative skin.

function get_template_file($filename, $tpl)
{
    // if we're requesting a JavaScript file, serve from 'default' regardless
    if (substr($filename, -3) == ".js") {
        return "tpl/default/".$filename;
    }
    else {
        $file = "tpl/{$tpl}/{$filename}";
        if (is_file($file) && is_readable($file)) {
            return $file;
        }
        else {
            $tpl = "default";
            return $file; // try return 'default' filepath
        }
    }
}
Martin Bean