Location: PHPKode > scripts > PHP/JavaScript Data Translation > php-javascript-data-translation/index.php
<?php
/**
 * Library for facilitating PHP and JavaScript interoperation
 * @author Erik Elmore <hide@address.com>
 * @version 0.1
 * @copyright Copyright &copy; 2006, Erik Elmore
 * @license http://www.opensource.org/licenses/gpl-license.php GNU General Public License
 * @package php2js
 */
/**
 * Facilitates passing PHP data down to JavaScript
 *
 * This object will take nearly any kind of PHP variable and output a string
 * that can be echoed into the body of a document as javascript.
 *
 * This is a static class that cannot be instantiated via 'new Php2js;'--an
 * exception will be thrown if you try.  This class is also declared using the
 * 'final' keyword to prevent derivatives.  The reason for this is the classing
 * is really only being used for namespacing and hiding the methods that don't
 * need to be accessible outside of the library.
 *
 * @package php2js
 */
final class Php2Js {
    /**
     * Private constructor
     * This class is 100% static, no instances.
     * @access private
     */
    private function __construct () {}
    /**
     * Converts arbitrary PHP data into JavaScript code
     *
     * This method is a general use function that accepts almost any kind of
     * PHP variable and returns a string that can be echoed into the body of
     * a document--such as in the head tag of HTML documents or you can even
     * (assuming your webserver is configured to make PHP process .js files)
     * dynamically create the entire contents of JavaScript files.
     *
     * Generally, just supply $var and the translated string is returned. This
     * function does not add any semicolons or newlines to the end of the
     * generated string.
     *
     * Notes:
     * - Exceptions will be thrown if it encounters a data type that it does not
     *   support or an object property/array index that can not be used as a
     *   JavaScript variable name.
     * - Private class members cannot be retrieved from outside their own class
     *   gracefully from the outside.  This class will simply ignore them.  If
     *   you really need private members translated, you can add a method to
     *   your class that returns an array like the one returned from
     *   get_object_vars() and pass that array to this function.
     * - Arrays with contiguous unsigned integer indices that start with 0
     *   will be represented as JavaScript arrays. Arrays with either negative,
     *   non-integer, non-contiguous, or non-numeric indices will be represented
     *   in JavaScript as objects.
     *
     * @param mixed var
     * @return string
     */
    public static function toJs( $var ) {
        return self::doTranslation( $var );
    }
   
    /**
     * Generate code to initialize JavaScript Image objects
     *
     * This function accepts a string or an array of strings and generates lines
     * of Javascript to create new Image() objects and set their src property.
     * This is useful if you have an array of images that you would like to
     * have precached by the browser.
     *
     * You must specify the name of the variable in $info, otherwise an exception
     * is thrown.  If $var is an array, $info will be used as the resulting
     * JavaScript array's name, and the array indices will be used as indices
     * or property names where appropriate.
     *
     * Note: Arrays with contiguous unsigned integer indices that start with 0
     * will be represented as JavaScript arrays. Arrays with either negative,
     * non-integer, non-contiguous, or non-numeric indices will be represented
     * in JavaScript as objects.
     *
     * @param mixed var
     * @param string info
     * @return string
     */
    public static function toJsImages( $var, $info ) {
        if( !self::validateJsVarName($info) ) {
            throw new Php2JsError("\$info must be a valid JavaScript variable name");
        }
       
        if( is_string($var) ) {
            $js  = str_repeat(self::$nStr, self::$nLevel);
            $js .= "var {$info} = new Image();\n";
            $js .= "{$info}.src = " . self::doString($var). ";\n";
            return $js;
        }
       
        if( !is_array($var) ) {
            throw new Php2JsError("\$var must either be a string or an array of strings");
        }
       
        $simple = self::isSimpleArray($var);
        ksort($var);
        $i = 0;
        $js  = str_repeat(self::$nStr, self::$nLevel);
        $js .= "var {$info} = new Array();\n";
        foreach( $var as $k => $v ) {
            if( $simple ) {
                $key = $i;
                $i++;
            }
            else {
                if( !self::validateJsPropName($k . "") ) {
                    throw new Php2JsError("$k (array index) is not a valid JavaScript variable name");
                }
                $key = $k;
            }
            $js .= str_repeat(self::$nStr, self::$nLevel);
            $js .= "{$info}['{$key}'] = new Image();\n";
            $js .= str_repeat(self::$nStr, self::$nLevel);
            $js .= "{$info}['{$key}'].src = " . self::doString($v) . ";\n";
        }
        return $js;
    }
   
    /**
     * Set the initial indentation level
     *
     * This function allows you to set the initial level of indentation before
     * translating a variable.  An exception is thrown if $level is not an int.
     *
     * @param int level
     */
    public static function setNestingLevel( $level ) {
        if( is_numeric($level) ? (intval(0+$level) == $x) : FALSE ) {
            throw new Php2JsError("\$level must be an integer value");
        }
        self::$nLevel = $level;
    }

    /**
     * Set the indentation string
     *
     * This function allows you to set the indentation string.  The specified
     * string is printed out once for each nesting level on every line.
     *
     * @param string str
     */
    public static function setNestingStr( $str ) {
        if( !is_string($var) ) {
            throw new Php2JsError("\$str must be a string");
        }
        self::$nStr = $str;
    }
   
    // Start DocBlock template area for internal library functions
    /**#@+
     * @internal
     */
   
    /**
     * Keeper of the current nesting level
     *
     * This member variable is used to track the current nesting level of the
     * JavaScript output.  It can be set externally via the setNestingLevel
     * method to allow for nesting to start at a specific level of indentation.
     *
     * @var integer
     */
    private static $nLevel = 0;
   
    /**
     * Used to create nesting effect
     *
     * This string is used to pad the JavaScript source code with white space
     * while iterating through arrays and object properties.  It is 4 spaces by
     * default, but it can be changed via setNestingStr.
     *
     * @var string
     */
    private static $nStr = "    ";
   
    /**
     * List of JavaScript keywords
     * @var array
     */
    private static $jsKeywords = array (
        'abstract' => 'abstract',
        'boolean' => 'boolean',
        'break' => 'break',
        'byte' => 'byte',
        'case' => 'case',
        'catch' => 'catch',
        'char' => 'char',
        'class' => 'class',
        'const' => 'const',
        'continue' => 'continue',
        'debugger' => 'debugger',
        'default' => 'default',
        'delete' => 'delete',
        'do' => 'do',
        'double' => 'double',
        'else' => 'else',
        'enum' => 'enum',
        'export' => 'export',
        'extends' => 'extends',
        'false' => 'false',
        'final' => 'final',
        'finally' => 'finally',
        'float' => 'float',
        'for' => 'for',
        'function' => 'function',
        'goto' => 'goto',
        'if' => 'if',
        'implements' => 'implements',
        'import' => 'import',
        'in' => 'in',
        'instanceof' => 'instanceof',
        'int' => 'int',
        'interface' => 'interface',
        'long' => 'long',
        'native' => 'native',
        'new' => 'new',
        'null' => 'null',
        'package' => 'package',
        'private' => 'private',
        'protected' => 'protected',
        'public' => 'public',
        'return' => 'return',
        'short' => 'short',
        'static' => 'static',
        'switch' => 'switch',
        'synchronized' => 'synchronized',
        'this' => 'this',
        'throw' => 'throw',
        'throws' => 'throws',
        'transient' => 'transient',
        'true' => 'true',
        'try' => 'try',
        'typeof' => 'typeof',
        'var' => 'var',
        'void' => 'void',
        'while' => 'while',
        'with' => 'with',
    );
   
    /**
     * Begin processing a variable
     *
     * This function is utilized directly by the public methods to begin
     * processing a variable and translate it to JavaScript.  It will also
     * call itself and set $recurring = TRUE when it encounters an array or
     * object.  This function will throw an exception when it encounters a
     * variable type that it does not support.  $recurring is not used now.
     *
     * Note: Detection of "simple" arrays has been disabled.  All arrays are
     * now converted to objects to preserve key-to-value relationships.
     *
     * @param mixed var
     * @param boolean recurring
     */
    private static function doTranslation( $var, $recurring = FALSE ) {
        $var = is_object($var) ? get_object_vars($var) : $var;
        if( is_array($var) ) {
            $wasScalar = FALSE;
            $simple = self::isSimpleArray($var);
            $lb = $simple ? "[" : "{";
            $rb = $simple ? "]" : "}";
            $js = "{$lb}\n";
            ksort($var);
        }
        else {
            $wasScalar = TRUE;
            $simple = TRUE;
            $var = array($var);
            $lb = "";
            $rb = "";
            $js = "";
        }
        self::$nLevel++;
       
        $c = count($var);
        $i = 0;
        foreach( $var as $k => $v )  {
            $left  = $wasScalar ? "" : str_repeat(self::$nStr, self::$nLevel);
            $left .= $simple ? "" : "'{$k}' : ";
            if( !$simple && !self::validateJsPropName($k) ) {
                throw new Php2JsError("'$k' is not a valid JavaScript variable name");
            }
           
            if( is_array($v) || is_object($v) ) {
                $js .= $left . self::doTranslation($v, TRUE);
            }
            else if( is_bool($v) ) {
                $js .= $left . self::doBool($v);
            }
            else if( is_null($v) ) {
                $js .= $left . self::doNull($v);
            }
            else if( is_int($v) || is_float($v)) {
                $js .= $left . self::doNumber($v);
            }
            else if( is_string($v) ) {
                $js .= $left . self::doString($v);
            }
            else {
                throw new Php2JsError("Cannot continue translation, unsupported type: " . gettype($v));
            }
           
            $i++;
            if( $i < $c ) {
                $js .= ",\n";
            }
            else {
                $js .= $wasScalar ? "" : "\n";
            }
        }
        self::$nLevel--;
        $tail = str_repeat(self::$nStr, self::$nLevel) . $rb;
        $js .= $wasScalar ? "" : $tail;
        return $js;
    }
   
    /**
     * Inline translation of a boolean to JavaScript
     *
     * This function accepts a boolean as input and returns the JavaScript
     * representation.  It is intended that this function be used for inline
     * translation--it does not add newlines, semicolons, or declarations.
     *
     * An Exception is thrown if $var is not boolean.
     *
     * @param boolean var
     * @return string
     */     
    private static function doBool( $var ) {
        if( !is_bool($var) ) {
            throw new Php2JsError("\$var must be a boolean");
        }
        $js = $var ? "true" : "false";
        return $js;
    }

    /**
     * Inline translation of NULL to JavaScript
     *
     * This function accepts a null type as input and returns the JavaScript
     * representation.  It is intended that this function be used for inline
     * translation--it does not add newlines, semicolons, or declarations.
     *
     * An Exception is thrown if $var is not null.
     *
     * @param null var
     * @return string
     */
    private static function doNull( $var ) {
        if( !is_null($var) ) {
            throw new Php2JsError("\$var must be null");
        }
        return "null";
    }
   
    /**
     * Inline translation of a numeric value JavaScript
     *
     * This function accepts a numeric as input and returns the JavaScript
     * representation.  It is intended that this function be used for inline
     * translation--it does not add newlines, semicolons, or declarations.
     *
     * An Exception is thrown if $var is not a number.
     *
     * @param null var
     * @return string
     */
    private static function doNumber( $var ) {
        if( !is_numeric($var) ) {
            throw new Php2JsError("\$var is not numeric");
        }
        $js = "" . $var;
        return $js;
    }

    /**
     * Inline translation of a string to JavaScript
     *
     * This function accepts a string as input and returns the JavaScript
     * representation.  It is intended that this function be used for inline
     * translation--it does not add newlines, semicolons, or declarations; and
     * should ONLY be applied to strings that should be escaped inside quotes.
     * This means don't use it on variable names, object property names, or
     * associative array indices. Single quote marks are added.
     *
     * An exception is thrown if $var is not a string.
     *
     * @param string var
     * @return string
     */
    private static function doString( $var ) {
        if( !is_string($var) ) {
            throw new Php2JsError("\$var must be a string");
        }
    $js = "'" . self::escapeToJs($var) . "'";
    return $js;
    }
   
    /**
     * Validates user-supplied JavaScript variable names
     *
     * This function will check a string passed in via the API and verify that
     * it can be used as a variable name in JavaScript.  It returns FALSE if
     * the specified name is not legal and otherwise returns TRUE.  If $name is
     * not a string, an exception is thrown.
     *
     * @param string var
     * @return boolean
     */
    private static function validateJsVarName( $name ) {
        if( !is_string($name) ) {
            throw new Php2JsError("\$name must be a string");
        }
        // cannot be a key word of javascript
        if( array_key_exists($name, self::$jsKeywords) ) {
            return FALSE;
        }
        // first letter must be a letter
        // can only be made of letters, numbers, or underscore
        $search = "/^[a-zA-Z]{1}[a-zA-Z0-9_]*$/";
        if( !preg_match($search, $name) ) {
            return FALSE;
        }
        return TRUE;
    }
   
    /**
     * Validates user-supplied JavaScript array/object property names
     *
     * This function will check a string passed in via the API and verify that
     * it can be used as a property name in JavaScript.  It returns FALSE if
     * the specified name is not legal and otherwise returns TRUE.  If $name is
     * not a string, an exception is thrown.
     *
     * @param string var
     * @return boolean
     */
    private static function validateJsPropName( $name ) {
        if( !is_string($name) && !is_numeric($name) ) {
            throw new Php2JsError("\$name must be a string");
        }
        // cannot be a key word of javascript
        if( array_key_exists($name, self::$jsKeywords) ) {
            return FALSE;
        }
        // first letter must be a letter
        // can only be made of letters, numbers, or underscore
        // UNLESS the whole thing is an integer
        $s1 = "/^[a-zA-Z]{1}[a-zA-Z0-9_]*$/";
        $s2 = "/^[0-9]*$/";
        if( !(preg_match($s1, $name) || preg_match($s2, $name)) ) {
            return FALSE;
        }
        return TRUE;
    }
   
    /**
     * Escapes strings so they are valid in JavaScript
     *
     * This function accepts a PHP string as input and returns a string that will
     * represent the input string in JavaScript.  This function does not add
     * quotation marks.  An exception is thrown if the input is not a string.
     *
     * @param string var
     */
    private static function escapeToJs( $var ) {
        if( !is_string($var) ) {
            throw new Php2JsError("\$var must be an array");
        }
        $var = str_replace(array('\\', "'"), array("\\\\", "\\'"), $var);
        $var = preg_replace('#([\x00-\x1F])#e', '"\x" . sprintf("%02x", ord("\1"))', $var);
        return $var;
    }
   
    /**
     * Determines if a PHP array's indices are compatible with JavaScript
     *
     * This function will examine the indices of a PHP array and determine its
     * suitability for a direct conversion to a JavaScript array.  It checks
     * the array keys to make sure they are all contiguous unsigned integers and
     * returns the appropriate boolean value.
     *
     * An exception is thrown if $var is not an array.
     * @param array var
     * @return boolean
     * @deprecated
     */
    private static function isSimpleArray( $var ) {
        if( !is_array($var) ) {
            throw new Php2JsError("\$var must be an array");
        }
        $c = count($var);
        for( $i = 0; $i < $c; $i++ ) {
            if( !array_key_exists($i, $var) ) {
                return FALSE;
            }
        }
        return TRUE;
    }

    // End DocBlock template area
    /**#@-*/
}

/**
 * Class for throwing exceptions related to this library
 *
 * When any exceptions are thrown by this library, it throws an object of this
 * class
 *
 * @package php2js
 */
class Php2JsError extends Exception {}
?>
Return current item: PHP/JavaScript Data Translation