Resource Loader for PHP Web Applications

I wrote a resource loader class for PHP web applications. The class will help to manage loading of JavaScripts and CSSs we include within our web pages. Usually these JavaScripts and CSSs have dependencies with other scripts and or CSSs. For example, we have to load jQuery script file before any script that utilise jQuery functionalities within the code. And we have to ensure that we are including the scripts in the correct order. By using this Resource loader class we can forget about it.

Once initialised, we can register script and/or CSS style links or raw scripts and/or CSS styles with resource loader object. Then finally we can render everything in correct order in our web page to deliver to client side browser.

Source code

<?php

/**
 * @author Junaid P V (http://junaidpv.in)
 * @license GPLv3
 */

/**
 * Class that helps to load resource easily, such as JavaScripts, CSSs
 *
 * @version 1.0
 */
class Resource_loader {

  private $libraryResources = array();
  private $resources = array();

  /**
   * Add resources that reside on disk.
   *
   * @param array $resources
   */
  public function add($resources) {
    foreach ($resources as $name => $resource) {      
      // It is always resource links added with this method
      $tmpArray = array('type' => 'link');

      // Add scripts if given
      if (isset($resource['scripts'])) {
        $tmpArray['scripts'] = $this->alwaysArray($resource['scripts']);
      }

      // Add styles if given
      if (isset($resource['styles'])) {
        $tmpArray['styles'] = $this->alwaysArray($resource['styles']);
      }

      if (isset($resource['dependencies'])) {
        $tmpArray['dependencies'] = $this->alwaysArray(
          $resource['dependencies']
        );
      }

      $this->resources[$name] = $tmpArray;
    }
  }

  /**
   * Add library resources.
   * Resource added by this method will be loaded
   * when there is at leat one dependance for it
   *
   * @param array $resources
   */
  public function addLibrary($resources) {
    foreach ($resources as $name => $resource) {
      // It is always resource links added with this method
      $tmpArray = array('type' => 'link');

      // Add scripts if given
      if (isset($resource['scripts'])) {
        $tmpArray['scripts'] = $this->alwaysArray($resource['scripts']);
      }

      // Add styles if given
      if (isset($resource['styles'])) {
        $tmpArray['styles'] = $this->alwaysArray($resource['styles']);
      }

      if (isset($resource['dependencies'])) {
        $tmpArray['dependencies'] = $this->alwaysArray(
          $resource['dependencies']
        );
      }

      $this->libraryResources[$name] = $tmpArray;
    }
  }

  /**
   * Add resouces (JS and CSS) as raw string.
   *
   * @param array $resources
   */
  public function addRaw($resources) {
    foreach ($resources as $name => $resource) {
      // It is always raw resources added by this method
      $tmpArray = array('type' => 'raw');

      if (isset($resource['scripts'])) {
        $tmpArray['scripts'] = $this->alwaysArray($resource['scripts']);
      }
      if (isset($resource['styles'])) {
        $tmpArray['styles'] = $this->alwaysArray($resource['styles']);
      }
      if (isset($resource['dependencies'])) {
        $tmpArray['dependencies'] = $this->alwaysArray(
          $resource['dependencies']
        );
      }

      $this->resources[$name] = $tmpArray;
    }
  }

  /**
   * Add raw library resources.
   * Resource added by this method will be loaded
   * when there is at leat one dependance for it
   *
   * @param array $resources
   */
  public function addRawLibrary($resources) {
    foreach ($resources as $name => $resource) {
      // It is always raw resources added by this method
      $tmpArray = array('type' => 'raw');

      if (isset($resource['scripts'])) {
        $tmpArray['scripts'] = $this->alwaysArray($resource['scripts']);
      }
      if (isset($resource['styles'])) {
        $tmpArray['styles'] = $this->alwaysArray($resource['styles']);
      }
      if (isset($resource['dependencies'])) {
        $tmpArray['dependencies'] =
        $this->alwaysArray($resource['dependencies']);
      }

      $this->libraryResources[$name] = $tmpArray;
    }
  }


  /**
   * Returns HTML snippet that can be inserted on web pages
   * to be send to client.
   * Caution: This funtion must not be called more than once.
   *
   * @return string
   */
  public function render() {
    // Get all resources in correct order.
    $ordered = $this->getOrdered();

    // Start with a blank html snippet.
    $html = '';
    foreach ($ordered as $res) {
      if ($res['type'] == 'raw') {
        // It is a raw resouce, we need to add the source code.
        if (isset($res['scripts'])) {
          $html .= "\n".'<script type="text/javascript">';
          foreach ($res['scripts'] as $script) {
            $html .= "\n" . $script;
          }
          $html .= '</script>';
        }
        if (isset($res['styles'])) {
          $html .= "\n".'<style type="text/css">';
          foreach ($res['styles'] as $style) {
            $html .= "\n" . $style;
          }
          $html .= '</style>';
        }
      } elseif ($res['type'] == 'link') {
        if (isset($res['scripts'])) {
          foreach ($res['scripts'] as $script) {
            $html .= "\n" . '<script type="text/javascript" src="'
                . $script . '"></script>';
          }
        }
        if (isset($res['styles'])) {
          foreach ($res['styles'] as $style) {
            $html .= "\n"
              . '<link rel="stylesheet" type="text/css" href="'
              . $style . '" />';
          }
        }
      }
    }
    return $html;
  }

  /**
   * Get resource array having all resources in correct order.
   */
  private function getOrdered() {
    // Get the unordered resource array.
    $source = $this->resources;
    // Create a blank ordered array to start with.
    $ordered = array();

    // Itereate over all items in unordered resource array to put every
    // item in correct order.
    while (true) {
      if (count($source) == 0) {
        // No more item in $source, let's stop it
        break;
      }

      // Get frist item in $source
      $pair = $this->array_kshift($source);
      // and put that item in correct order at $ordered.
      // putItemAtCorrectPosition will manage dependent items.
      $this->putItemAtCorrectPosition($pair[0], $pair[1], $ordered);
    }
    return $ordered;
  }

  /**
   * Put the specified item at correct position in $ordered.
   * If depended item(s) are in $source they will be put before the item.
   *
   * @param array $item
   * @param array $ordered
   * @param array $source
   */
  private function putItemAtCorrectPosition($resourceName, $resource, &$ordered) {
    // Let's check about dependecies before putting item in $ordered.
    if (isset($resource['dependencies'])) {
      // Check whether any depended resource in $source
      // since all depended items should be in $ordered before
      // the current item put in.
      foreach ($resource['dependencies'] as $subject) {
        if (array_key_exists($subject, $ordered)) {
          // Depended is already included
          continue;
        }
        else {
          $source = null;
          if (array_key_exists($subject, $this->libraryResources)) {
            // Dependency is on library
            $source = $this->libraryResources;
          }
          elseif (array_key_exists($subject, $this->resources)) {
            // Dependency is on normal resource
            $source = $this->resources;
          }
          else {
            // Depended resource is not available at all.
            // It is mistake and let us throw and
            // exception informing about it.
            throw new Exception(
              "Depended item '$subject' could not be found"
            );
          }

          // Take a copy of it from source
          $depended = $source[$subject];
          // Remove it from source
          unset($source[$subject]);
          // And put the depended item in $ordered
          // at correct position
          $this->putItemAtCorrectPosition(
            $subject,
            $depended,
            $ordered
          );
        }
      }
    }
    // We have already put every depended items in correct position,
    // so put current item at the end of $ordered.
    $ordered[$resourceName] = $resource;
  }

  private function alwaysArray($value) {
    return (is_array($value) ? $value : array($value));
  }

  /**
   * Array shift with key
   *
   * @param array $arr
   * @return array
   */
  private function array_kshift(&$arr) {
    list($k) = array_keys($arr);
    $r  = array($k, $arr[$k]);
    unset($arr[$k]);
    return $r;
  }

  /**
   * Sometimes some libraries needs to be loaded even some other script has
   * no dependency on them, like posabsolute jquery validation.
   *
   * @param string $name
   */
  public function alwaysLoadLibrary($name) {
    if (!array_key_exists($name, $this->resources)) {
      // No need to add if the library is already as normal resource

      if(array_key_exists($name, $this->libraryResources)) {
        // Move resource from library to normal
        // so it will be loaded always
        $this->resources[$name] = $this->libraryResources[$name];
        // then remove from library
        unset ($this->libraryResources[$name]);
      }
      else {
        // Resource is not on both library and normal resources
        throw new Exception(
              "Library '$name' not yet added!"
            );
      }
    }
  }
}

(Copy of the script hosted at https://gist.github.com/1175799.js)

Typical Usage

<?php
// Create object
$resouceLoader = ResourceLoader();

// Somewhere in code
$resourceLoader->add( array (
  'jquery' => array(
    'styles' => 'css/themename/jquery-ui-1.8.16.custom.css',
    'scripts' => array( 'js/jquery-1.4.4.min.js', 'js/jquery-ui-1.8.16.custom.min.js'),
  ),
) );

// Again, somewhere
$resourceLoader->add( array (
  'ourcode' => array(
    'styles' => 'css/some-css.css',
    'scripts' => 'js/some-js-that-depend-on-jquery.js',
    'dependencies' => 'jquery',
  ),
) );


// Finally, in template/theme
echo $resouceLoader->render();

(Copy of the script hosted at Github gist https://gist.github.com/1175812.js)

Originally I wrote this as a library class for one of my projects using CodeIgniter. If you want to use this with your CodeIgniter project, just rename class name appropriately like 'MY_Resource_loader' and put it in a file named 'Resource_loader.php' under 'application/libraries' directory. Then within your controller, load it by calling:

$this->load->library('resource_loader');

then, use it like:

$this->resource_loader->add(array(...));

and so on as described above in usage part.