Created
October 30, 2012 17:42
-
-
Save bobthecow/3981780 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// load n classes from the filesystem | |
if (count($argv) !== 3) { | |
throw new InvalidArgumentException('usage: php apc.php numtemplates size'); | |
} | |
$numtemplates = $argv[1]; | |
$templatesize = $argv[2]; | |
$start = microtime(true); | |
for ($i = 0; $i < $numtemplates; $i++) { | |
$name = './tmp/' . $templatesize . $i . '.php'; | |
require $name; | |
} | |
$end = microtime(true); | |
echo $end - $start . "\n"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
for i in 1 10 100 1000; do | |
./go.sh $i tiny | |
./go.sh $i small | |
./go.sh $i medium | |
./go.sh $i large | |
done |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# | |
# usage: ./go.sh num size | |
# | |
# example: | |
# ./go.sh 10 small | |
# ./go.sh 50 large | |
# ./go.sh 200 medium | |
# | |
rm -r ./tmp | |
mkdir -p ./tmp | |
php prepare.php $1 $2 | |
for i in {1..10}; do | |
php apc.php $1 $2 >> tmp/apc.csv | |
done | |
for i in {1..10}; do | |
php memcache.php $1 $2 >> tmp/memcache.csv | |
done | |
php report.php $1 $2 >> results.csv | |
tail -1 results.csv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* A Mustache implementation in PHP. | |
* | |
* {@link http://defunkt.github.com/mustache} | |
* | |
* Mustache is a framework-agnostic logic-less templating language. It enforces separation of view | |
* logic from template files. In fact, it is not even possible to embed logic in the template. | |
* | |
* This is very, very rad. | |
* | |
* @author Justin Hileman {@link http://justinhileman.com} | |
*/ | |
class Mustache{{ NUMBER }} { | |
const VERSION = '1.1.0'; | |
const SPEC_VERSION = '1.1.2'; | |
/** | |
* Should this Mustache throw exceptions when it finds unexpected tags? | |
* | |
* @see self::_throwsException() | |
*/ | |
protected $_throwsExceptions = array( | |
MustacheException::UNKNOWN_VARIABLE => false, | |
MustacheException::UNCLOSED_SECTION => true, | |
MustacheException::UNEXPECTED_CLOSE_SECTION => true, | |
MustacheException::UNKNOWN_PARTIAL => false, | |
MustacheException::UNKNOWN_PRAGMA => true, | |
); | |
// Override the escaper function. Defaults to `htmlspecialchars`. | |
protected $_escape; | |
// Override charset passed to htmlentities() and htmlspecialchars(). Defaults to UTF-8. | |
protected $_charset = 'UTF-8'; | |
/** | |
* Pragmas are macro-like directives that, when invoked, change the behavior or | |
* syntax of Mustache. | |
* | |
* They should be considered extremely experimental. Most likely their implementation | |
* will change in the future. | |
*/ | |
/** | |
* The {{%UNESCAPED}} pragma swaps the meaning of the {{normal}} and {{{unescaped}}} | |
* Mustache tags. That is, once this pragma is activated the {{normal}} tag will not be | |
* escaped while the {{{unescaped}}} tag will be escaped. | |
* | |
* Pragmas apply only to the current template. Partials, even those included after the | |
* {{%UNESCAPED}} call, will need their own pragma declaration. | |
* | |
* This may be useful in non-HTML Mustache situations. | |
*/ | |
const PRAGMA_UNESCAPED = 'UNESCAPED'; | |
/** | |
* Constants used for section and tag RegEx | |
*/ | |
const SECTION_TYPES = '\^#\/'; | |
const TAG_TYPES = '#\^\/=!<>\\{&'; | |
protected $_otag = '{{'; | |
protected $_ctag = '}}'; | |
protected $_tagRegEx; | |
protected $_template = ''; | |
protected $_context = array(); | |
protected $_partials = array(); | |
protected $_pragmas = array(); | |
protected $_pragmasImplemented = array( | |
self::PRAGMA_UNESCAPED | |
); | |
protected $_localPragmas = array(); | |
/** | |
* Mustache class constructor. | |
* | |
* This method accepts a $template string and a $view object. Optionally, pass an associative | |
* array of partials as well. | |
* | |
* Passing an $options array allows overriding certain Mustache options during instantiation: | |
* | |
* $options = array( | |
* // `escape` -- custom escaper callback; must be callable. | |
* 'escape' => function($text) { | |
* return htmlspecialchars($text, ENT_COMPAT, 'UTF-8'); | |
* }, | |
* | |
* // `charset` -- must be supported by `htmlspecialentities()`. defaults to 'UTF-8' | |
* 'charset' => 'ISO-8859-1', | |
* | |
* // opening and closing delimiters, as an array or a space-separated string | |
* 'delimiters' => '<% %>', | |
* | |
* // an array of pragmas to enable/disable | |
* 'pragmas' => array( | |
* Mustache::PRAGMA_UNESCAPED => true | |
* ), | |
* | |
* // an array of thrown exceptions to enable/disable | |
* 'throws_exceptions' => array( | |
* MustacheException::UNKNOWN_VARIABLE => false, | |
* MustacheException::UNCLOSED_SECTION => true, | |
* MustacheException::UNEXPECTED_CLOSE_SECTION => true, | |
* MustacheException::UNKNOWN_PARTIAL => false, | |
* MustacheException::UNKNOWN_PRAGMA => true, | |
* ), | |
* ); | |
* | |
* @access public | |
* @param string $template (default: null) | |
* @param mixed $view (default: null) | |
* @param array $partials (default: null) | |
* @param array $options (default: array()) | |
* @return void | |
*/ | |
public function __construct($template = null, $view = null, $partials = null, array $options = null) { | |
if ($template !== null) $this->_template = $template; | |
if ($partials !== null) $this->_partials = $partials; | |
if ($view !== null) $this->_context = array($view); | |
if ($options !== null) $this->_setOptions($options); | |
} | |
/** | |
* Helper function for setting options from constructor args. | |
* | |
* @access protected | |
* @param array $options | |
* @return void | |
*/ | |
protected function _setOptions(array $options) { | |
if (isset($options['escape'])) { | |
if (!is_callable($options['escape'])) { | |
throw new InvalidArgumentException('Mustache constructor "escape" option must be callable'); | |
} | |
$this->_escape = $options['escape']; | |
} | |
if (isset($options['charset'])) { | |
$this->_charset = $options['charset']; | |
} | |
if (isset($options['delimiters'])) { | |
$delims = $options['delimiters']; | |
if (!is_array($delims)) { | |
$delims = array_map('trim', explode(' ', $delims, 2)); | |
} | |
$this->_otag = $delims[0]; | |
$this->_ctag = $delims[1]; | |
} | |
if (isset($options['pragmas'])) { | |
foreach ($options['pragmas'] as $pragma_name => $pragma_value) { | |
if (!in_array($pragma_name, $this->_pragmasImplemented, true)) { | |
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA); | |
} | |
} | |
$this->_pragmas = $options['pragmas']; | |
} | |
if (isset($options['throws_exceptions'])) { | |
foreach ($options['throws_exceptions'] as $exception => $value) { | |
$this->_throwsExceptions[$exception] = $value; | |
} | |
} | |
} | |
/** | |
* Mustache class clone method. | |
* | |
* A cloned Mustache instance should have pragmas, delimeters and root context | |
* reset to default values. | |
* | |
* @access public | |
* @return void | |
*/ | |
public function __clone() { | |
$this->_otag = '{{'; | |
$this->_ctag = '}}'; | |
$this->_localPragmas = array(); | |
if ($keys = array_keys($this->_context)) { | |
$last = array_pop($keys); | |
if ($this->_context[$last] instanceof Mustache) { | |
$this->_context[$last] =& $this; | |
} | |
} | |
} | |
/** | |
* Render the given template and view object. | |
* | |
* Defaults to the template and view passed to the class constructor unless a new one is provided. | |
* Optionally, pass an associative array of partials as well. | |
* | |
* @access public | |
* @param string $template (default: null) | |
* @param mixed $view (default: null) | |
* @param array $partials (default: null) | |
* @return string Rendered Mustache template. | |
*/ | |
public function render($template = null, $view = null, $partials = null) { | |
if ($template === null) $template = $this->_template; | |
if ($partials !== null) $this->_partials = $partials; | |
$otag_orig = $this->_otag; | |
$ctag_orig = $this->_ctag; | |
if ($view) { | |
$this->_context = array($view); | |
} else if (empty($this->_context)) { | |
$this->_context = array($this); | |
} | |
$template = $this->_renderPragmas($template); | |
$template = $this->_renderTemplate($template); | |
$this->_otag = $otag_orig; | |
$this->_ctag = $ctag_orig; | |
return $template; | |
} | |
/** | |
* Wrap the render() function for string conversion. | |
* | |
* @access public | |
* @return string | |
*/ | |
public function __toString() { | |
// PHP doesn't like exceptions in __toString. | |
// catch any exceptions and convert them to strings. | |
try { | |
$result = $this->render(); | |
return $result; | |
} catch (Exception $e) { | |
return "Error rendering mustache: " . $e->getMessage(); | |
} | |
} | |
/** | |
* Internal render function, used for recursive calls. | |
* | |
* @access protected | |
* @param string $template | |
* @return string Rendered Mustache template. | |
*/ | |
protected function _renderTemplate($template) { | |
if ($section = $this->_findSection($template)) { | |
list($before, $type, $tag_name, $content, $after) = $section; | |
$rendered_before = $this->_renderTags($before); | |
$rendered_content = ''; | |
$val = $this->_getVariable($tag_name); | |
switch($type) { | |
// inverted section | |
case '^': | |
if (empty($val)) { | |
$rendered_content = $this->_renderTemplate($content); | |
} | |
break; | |
// regular section | |
case '#': | |
// higher order sections | |
if ($this->_varIsCallable($val)) { | |
$rendered_content = $this->_renderTemplate(call_user_func($val, $content)); | |
} else if ($this->_varIsIterable($val)) { | |
foreach ($val as $local_context) { | |
$this->_pushContext($local_context); | |
$rendered_content .= $this->_renderTemplate($content); | |
$this->_popContext(); | |
} | |
} else if ($val) { | |
if (is_array($val) || is_object($val)) { | |
$this->_pushContext($val); | |
$rendered_content = $this->_renderTemplate($content); | |
$this->_popContext(); | |
} else { | |
$rendered_content = $this->_renderTemplate($content); | |
} | |
} | |
break; | |
} | |
return $rendered_before . $rendered_content . $this->_renderTemplate($after); | |
} | |
return $this->_renderTags($template); | |
} | |
/** | |
* Prepare a section RegEx string for the given opening/closing tags. | |
* | |
* @access protected | |
* @param string $otag | |
* @param string $ctag | |
* @return string | |
*/ | |
protected function _prepareSectionRegEx($otag, $ctag) { | |
return sprintf( | |
'/(?:(?<=\\n)[ \\t]*)?%s(?:(?P<type>[%s])(?P<tag_name>.+?)|=(?P<delims>.*?)=)%s\\n?/s', | |
preg_quote($otag, '/'), | |
self::SECTION_TYPES, | |
preg_quote($ctag, '/') | |
); | |
} | |
/** | |
* Extract the first section from $template. | |
* | |
* @access protected | |
* @param string $template | |
* @return array $before, $type, $tag_name, $content and $after | |
*/ | |
protected function _findSection($template) { | |
$regEx = $this->_prepareSectionRegEx($this->_otag, $this->_ctag); | |
$section_start = null; | |
$section_type = null; | |
$content_start = null; | |
$search_offset = 0; | |
$section_stack = array(); | |
$matches = array(); | |
while (preg_match($regEx, $template, $matches, PREG_OFFSET_CAPTURE, $search_offset)) { | |
if (isset($matches['delims'][0])) { | |
list($otag, $ctag) = explode(' ', $matches['delims'][0]); | |
$regEx = $this->_prepareSectionRegEx($otag, $ctag); | |
$search_offset = $matches[0][1] + strlen($matches[0][0]); | |
continue; | |
} | |
$match = $matches[0][0]; | |
$offset = $matches[0][1]; | |
$type = $matches['type'][0]; | |
$tag_name = trim($matches['tag_name'][0]); | |
$search_offset = $offset + strlen($match); | |
switch ($type) { | |
case '^': | |
case '#': | |
if (empty($section_stack)) { | |
$section_start = $offset; | |
$section_type = $type; | |
$content_start = $search_offset; | |
} | |
array_push($section_stack, $tag_name); | |
break; | |
case '/': | |
if (empty($section_stack) || ($tag_name !== array_pop($section_stack))) { | |
if ($this->_throwsException(MustacheException::UNEXPECTED_CLOSE_SECTION)) { | |
throw new MustacheException('Unexpected close section: ' . $tag_name, MustacheException::UNEXPECTED_CLOSE_SECTION); | |
} | |
} | |
if (empty($section_stack)) { | |
// $before, $type, $tag_name, $content, $after | |
return array( | |
substr($template, 0, $section_start), | |
$section_type, | |
$tag_name, | |
substr($template, $content_start, $offset - $content_start), | |
substr($template, $search_offset), | |
); | |
} | |
break; | |
} | |
} | |
if (!empty($section_stack)) { | |
if ($this->_throwsException(MustacheException::UNCLOSED_SECTION)) { | |
throw new MustacheException('Unclosed section: ' . $section_stack[0], MustacheException::UNCLOSED_SECTION); | |
} | |
} | |
} | |
/** | |
* Prepare a pragma RegEx for the given opening/closing tags. | |
* | |
* @access protected | |
* @param string $otag | |
* @param string $ctag | |
* @return string | |
*/ | |
protected function _preparePragmaRegEx($otag, $ctag) { | |
return sprintf( | |
'/%s%%\\s*(?P<pragma_name>[\\w_-]+)(?P<options_string>(?: [\\w]+=[\\w]+)*)\\s*%s\\n?/s', | |
preg_quote($otag, '/'), | |
preg_quote($ctag, '/') | |
); | |
} | |
/** | |
* Initialize pragmas and remove all pragma tags. | |
* | |
* @access protected | |
* @param string $template | |
* @return string | |
*/ | |
protected function _renderPragmas($template) { | |
$this->_localPragmas = $this->_pragmas; | |
// no pragmas | |
if (strpos($template, $this->_otag . '%') === false) { | |
return $template; | |
} | |
$regEx = $this->_preparePragmaRegEx($this->_otag, $this->_ctag); | |
return preg_replace_callback($regEx, array($this, '_renderPragma'), $template); | |
} | |
/** | |
* A preg_replace helper to remove {{%PRAGMA}} tags and enable requested pragma. | |
* | |
* @access protected | |
* @param mixed $matches | |
* @return void | |
* @throws MustacheException unknown pragma | |
*/ | |
protected function _renderPragma($matches) { | |
$pragma = $matches[0]; | |
$pragma_name = $matches['pragma_name']; | |
$options_string = $matches['options_string']; | |
if (!in_array($pragma_name, $this->_pragmasImplemented)) { | |
if ($this->_throwsException(MustacheException::UNKNOWN_PRAGMA)) { | |
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA); | |
} else { | |
return ''; | |
} | |
} | |
$options = array(); | |
foreach (explode(' ', trim($options_string)) as $o) { | |
if ($p = trim($o)) { | |
$p = explode('=', $p); | |
$options[$p[0]] = $p[1]; | |
} | |
} | |
if (empty($options)) { | |
$this->_localPragmas[$pragma_name] = true; | |
} else { | |
$this->_localPragmas[$pragma_name] = $options; | |
} | |
return ''; | |
} | |
/** | |
* Check whether this Mustache has a specific pragma. | |
* | |
* @access protected | |
* @param string $pragma_name | |
* @return bool | |
*/ | |
protected function _hasPragma($pragma_name) { | |
if (array_key_exists($pragma_name, $this->_localPragmas) && $this->_localPragmas[$pragma_name]) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/** | |
* Return pragma options, if any. | |
* | |
* @access protected | |
* @param string $pragma_name | |
* @return mixed | |
* @throws MustacheException Unknown pragma | |
*/ | |
protected function _getPragmaOptions($pragma_name) { | |
if (!$this->_hasPragma($pragma_name)) { | |
if ($this->_throwsException(MustacheException::UNKNOWN_PRAGMA)) { | |
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA); | |
} | |
} | |
return (is_array($this->_localPragmas[$pragma_name])) ? $this->_localPragmas[$pragma_name] : array(); | |
} | |
/** | |
* Check whether this Mustache instance throws a given exception. | |
* | |
* Expects exceptions to be MustacheException error codes (i.e. class constants). | |
* | |
* @access protected | |
* @param mixed $exception | |
* @return void | |
*/ | |
protected function _throwsException($exception) { | |
return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]); | |
} | |
/** | |
* Prepare a tag RegEx for the given opening/closing tags. | |
* | |
* @access protected | |
* @param string $otag | |
* @param string $ctag | |
* @return string | |
*/ | |
protected function _prepareTagRegEx($otag, $ctag, $first = false) { | |
return sprintf( | |
'/(?P<leading>(?:%s\\r?\\n)[ \\t]*)?%s(?P<type>[%s]?)(?P<tag_name>.+?)(?:\\2|})?%s(?P<trailing>\\s*(?:\\r?\\n|\\Z))?/s', | |
($first ? '\\A|' : ''), | |
preg_quote($otag, '/'), | |
self::TAG_TYPES, | |
preg_quote($ctag, '/') | |
); | |
} | |
/** | |
* Loop through and render individual Mustache tags. | |
* | |
* @access protected | |
* @param string $template | |
* @return void | |
*/ | |
protected function _renderTags($template) { | |
if (strpos($template, $this->_otag) === false) { | |
return $template; | |
} | |
$first = true; | |
$this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag, true); | |
$html = ''; | |
$matches = array(); | |
while (preg_match($this->_tagRegEx, $template, $matches, PREG_OFFSET_CAPTURE)) { | |
$tag = $matches[0][0]; | |
$offset = $matches[0][1]; | |
$modifier = $matches['type'][0]; | |
$tag_name = trim($matches['tag_name'][0]); | |
if (isset($matches['leading']) && $matches['leading'][1] > -1) { | |
$leading = $matches['leading'][0]; | |
} else { | |
$leading = null; | |
} | |
if (isset($matches['trailing']) && $matches['trailing'][1] > -1) { | |
$trailing = $matches['trailing'][0]; | |
} else { | |
$trailing = null; | |
} | |
$html .= substr($template, 0, $offset); | |
$next_offset = $offset + strlen($tag); | |
if ((substr($html, -1) == "\n") && (substr($template, $next_offset, 1) == "\n")) { | |
$next_offset++; | |
} | |
$template = substr($template, $next_offset); | |
$html .= $this->_renderTag($modifier, $tag_name, $leading, $trailing); | |
if ($first == true) { | |
$first = false; | |
$this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag); | |
} | |
} | |
return $html . $template; | |
} | |
/** | |
* Render the named tag, given the specified modifier. | |
* | |
* Accepted modifiers are `=` (change delimiter), `!` (comment), `>` (partial) | |
* `{` or `&` (don't escape output), or none (render escaped output). | |
* | |
* @access protected | |
* @param string $modifier | |
* @param string $tag_name | |
* @param string $leading Whitespace | |
* @param string $trailing Whitespace | |
* @throws MustacheException Unmatched section tag encountered. | |
* @return string | |
*/ | |
protected function _renderTag($modifier, $tag_name, $leading, $trailing) { | |
switch ($modifier) { | |
case '=': | |
return $this->_changeDelimiter($tag_name, $leading, $trailing); | |
break; | |
case '!': | |
return $this->_renderComment($tag_name, $leading, $trailing); | |
break; | |
case '>': | |
case '<': | |
return $this->_renderPartial($tag_name, $leading, $trailing); | |
break; | |
case '{': | |
// strip the trailing } ... | |
if ($tag_name[(strlen($tag_name) - 1)] == '}') { | |
$tag_name = substr($tag_name, 0, -1); | |
} | |
case '&': | |
if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) { | |
return $this->_renderEscaped($tag_name, $leading, $trailing); | |
} else { | |
return $this->_renderUnescaped($tag_name, $leading, $trailing); | |
} | |
break; | |
case '#': | |
case '^': | |
case '/': | |
// remove any leftover section tags | |
return $leading . $trailing; | |
break; | |
default: | |
if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) { | |
return $this->_renderUnescaped($modifier . $tag_name, $leading, $trailing); | |
} else { | |
return $this->_renderEscaped($modifier . $tag_name, $leading, $trailing); | |
} | |
break; | |
} | |
} | |
/** | |
* Returns true if any of its args contains the "\r" character. | |
* | |
* @access protected | |
* @param string $str | |
* @return boolean | |
*/ | |
protected function _stringHasR($str) { | |
foreach (func_get_args() as $arg) { | |
if (strpos($arg, "\r") !== false) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Escape and return the requested tag. | |
* | |
* @access protected | |
* @param string $tag_name | |
* @param string $leading Whitespace | |
* @param string $trailing Whitespace | |
* @return string | |
*/ | |
protected function _renderEscaped($tag_name, $leading, $trailing) { | |
$value = $this->_renderUnescaped($tag_name, '', ''); | |
if (isset($this->_escape)) { | |
$rendered = call_user_func($this->_escape, $value); | |
} else { | |
$rendered = htmlentities($value, ENT_COMPAT, $this->_charset); | |
} | |
return $leading . $rendered . $trailing; | |
} | |
/** | |
* Render a comment (i.e. return an empty string). | |
* | |
* @access protected | |
* @param string $tag_name | |
* @param string $leading Whitespace | |
* @param string $trailing Whitespace | |
* @return string | |
*/ | |
protected function _renderComment($tag_name, $leading, $trailing) { | |
if ($leading !== null && $trailing !== null) { | |
if (strpos($leading, "\n") === false) { | |
return ''; | |
} | |
return $this->_stringHasR($leading, $trailing) ? "\r\n" : "\n"; | |
} | |
return $leading . $trailing; | |
} | |
/** | |
* Return the requested tag unescaped. | |
* | |
* @access protected | |
* @param string $tag_name | |
* @param string $leading Whitespace | |
* @param string $trailing Whitespace | |
* @return string | |
*/ | |
protected function _renderUnescaped($tag_name, $leading, $trailing) { | |
$val = $this->_getVariable($tag_name); | |
if ($this->_varIsCallable($val)) { | |
$val = $this->_renderTemplate(call_user_func($val)); | |
} | |
return $leading . $val . $trailing; | |
} | |
/** | |
* Render the requested partial. | |
* | |
* @access protected | |
* @param string $tag_name | |
* @param string $leading Whitespace | |
* @param string $trailing Whitespace | |
* @return string | |
*/ | |
protected function _renderPartial($tag_name, $leading, $trailing) { | |
$partial = $this->_getPartial($tag_name); | |
if ($leading !== null && $trailing !== null) { | |
$whitespace = trim($leading, "\r\n"); | |
$partial = preg_replace('/(\\r?\\n)(?!$)/s', "\\1" . $whitespace, $partial); | |
} | |
$view = clone($this); | |
if ($leading !== null && $trailing !== null) { | |
return $leading . $view->render($partial); | |
} else { | |
return $leading . $view->render($partial) . $trailing; | |
} | |
} | |
/** | |
* Change the Mustache tag delimiter. This method also replaces this object's current | |
* tag RegEx with one using the new delimiters. | |
* | |
* @access protected | |
* @param string $tag_name | |
* @param string $leading Whitespace | |
* @param string $trailing Whitespace | |
* @return string | |
*/ | |
protected function _changeDelimiter($tag_name, $leading, $trailing) { | |
list($otag, $ctag) = explode(' ', $tag_name); | |
$this->_otag = $otag; | |
$this->_ctag = $ctag; | |
$this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag); | |
if ($leading !== null && $trailing !== null) { | |
if (strpos($leading, "\n") === false) { | |
return ''; | |
} | |
return $this->_stringHasR($leading, $trailing) ? "\r\n" : "\n"; | |
} | |
return $leading . $trailing; | |
} | |
/** | |
* Push a local context onto the stack. | |
* | |
* @access protected | |
* @param array &$local_context | |
* @return void | |
*/ | |
protected function _pushContext(&$local_context) { | |
$new = array(); | |
$new[] =& $local_context; | |
foreach (array_keys($this->_context) as $key) { | |
$new[] =& $this->_context[$key]; | |
} | |
$this->_context = $new; | |
} | |
/** | |
* Remove the latest context from the stack. | |
* | |
* @access protected | |
* @return void | |
*/ | |
protected function _popContext() { | |
$new = array(); | |
$keys = array_keys($this->_context); | |
array_shift($keys); | |
foreach ($keys as $key) { | |
$new[] =& $this->_context[$key]; | |
} | |
$this->_context = $new; | |
} | |
/** | |
* Get a variable from the context array. | |
* | |
* If the view is an array, returns the value with array key $tag_name. | |
* If the view is an object, this will check for a public member variable | |
* named $tag_name. If none is available, this method will execute and return | |
* any class method named $tag_name. Failing all of the above, this method will | |
* return an empty string. | |
* | |
* @access protected | |
* @param string $tag_name | |
* @throws MustacheException Unknown variable name. | |
* @return string | |
*/ | |
protected function _getVariable($tag_name) { | |
if ($tag_name === '.') { | |
return $this->_context[0]; | |
} else if (strpos($tag_name, '.') !== false) { | |
$chunks = explode('.', $tag_name); | |
$first = array_shift($chunks); | |
$ret = $this->_findVariableInContext($first, $this->_context); | |
foreach ($chunks as $next) { | |
// Slice off a chunk of context for dot notation traversal. | |
$c = array($ret); | |
$ret = $this->_findVariableInContext($next, $c); | |
} | |
return $ret; | |
} else { | |
return $this->_findVariableInContext($tag_name, $this->_context); | |
} | |
} | |
/** | |
* Get a variable from the context array. Internal helper used by getVariable() to abstract | |
* variable traversal for dot notation. | |
* | |
* @access protected | |
* @param string $tag_name | |
* @param array $context | |
* @throws MustacheException Unknown variable name. | |
* @return string | |
*/ | |
protected function _findVariableInContext($tag_name, $context) { | |
foreach ($context as $view) { | |
if (is_object($view)) { | |
if (method_exists($view, $tag_name)) { | |
return $view->$tag_name(); | |
} else if (isset($view->$tag_name)) { | |
return $view->$tag_name; | |
} | |
} else if (is_array($view) && array_key_exists($tag_name, $view)) { | |
return $view[$tag_name]; | |
} | |
} | |
if ($this->_throwsException(MustacheException::UNKNOWN_VARIABLE)) { | |
throw new MustacheException("Unknown variable: " . $tag_name, MustacheException::UNKNOWN_VARIABLE); | |
} else { | |
return ''; | |
} | |
} | |
/** | |
* Retrieve the partial corresponding to the requested tag name. | |
* | |
* Silently fails (i.e. returns '') when the requested partial is not found. | |
* | |
* @access protected | |
* @param string $tag_name | |
* @throws MustacheException Unknown partial name. | |
* @return string | |
*/ | |
protected function _getPartial($tag_name) { | |
if ((is_array($this->_partials) || $this->_partials instanceof ArrayAccess) && isset($this->_partials[$tag_name])) { | |
return $this->_partials[$tag_name]; | |
} | |
if ($this->_throwsException(MustacheException::UNKNOWN_PARTIAL)) { | |
throw new MustacheException('Unknown partial: ' . $tag_name, MustacheException::UNKNOWN_PARTIAL); | |
} else { | |
return ''; | |
} | |
} | |
/** | |
* Check whether the given $var should be iterated (i.e. in a section context). | |
* | |
* @access protected | |
* @param mixed $var | |
* @return bool | |
*/ | |
protected function _varIsIterable($var) { | |
return $var instanceof Traversable || (is_array($var) && !array_diff_key($var, array_keys(array_keys($var)))); | |
} | |
/** | |
* Higher order sections helper: tests whether the section $var is a valid callback. | |
* | |
* In Mustache.php, a variable is considered 'callable' if the variable is: | |
* | |
* 1. an anonymous function. | |
* 2. an object and the name of a public function, i.e. `array($SomeObject, 'methodName')` | |
* 3. a class name and the name of a public static function, i.e. `array('SomeClass', 'methodName')` | |
* | |
* @access protected | |
* @param mixed $var | |
* @return bool | |
*/ | |
protected function _varIsCallable($var) { | |
return !is_string($var) && is_callable($var); | |
} | |
} | |
/** | |
* MustacheException class. | |
* | |
* @extends Exception | |
*/ | |
class MustacheException{{ NUMBER }} extends Exception { | |
// An UNKNOWN_VARIABLE exception is thrown when a {{variable}} is not found | |
// in the current context. | |
const UNKNOWN_VARIABLE = 0; | |
// An UNCLOSED_SECTION exception is thrown when a {{#section}} is not closed. | |
const UNCLOSED_SECTION = 1; | |
// An UNEXPECTED_CLOSE_SECTION exception is thrown when {{/section}} appears | |
// without a corresponding {{#section}} or {{^section}}. | |
const UNEXPECTED_CLOSE_SECTION = 2; | |
// An UNKNOWN_PARTIAL exception is thrown whenever a {{>partial}} tag appears | |
// with no associated partial. | |
const UNKNOWN_PARTIAL = 3; | |
// An UNKNOWN_PRAGMA exception is thrown whenever a {{%PRAGMA}} tag appears | |
// which can't be handled by this Mustache instance. | |
const UNKNOWN_PRAGMA = 4; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* This file is part of Mustache.php. | |
* | |
* (c) 2012 Justin Hileman | |
* | |
* For the full copyright and license information, please view the LICENSE | |
* file that was distributed with this source code. | |
*/ | |
/** | |
* Mustache Compiler class. | |
* | |
* This class is responsible for turning a Mustache token parse tree into normal PHP source code. | |
*/ | |
class Mustache_Compiler{{ NUMBER }} | |
{ | |
private $sections; | |
private $source; | |
private $indentNextLine; | |
private $customEscape; | |
private $charset; | |
/** | |
* Compile a Mustache token parse tree into PHP source code. | |
* | |
* @param string $source Mustache Template source code | |
* @param string $tree Parse tree of Mustache tokens | |
* @param string $name Mustache Template class name | |
* @param bool $customEscape (default: false) | |
* @param string $charset (default: 'UTF-8') | |
* | |
* @return string Generated PHP source code | |
*/ | |
public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8') | |
{ | |
$this->sections = array(); | |
$this->source = $source; | |
$this->indentNextLine = true; | |
$this->customEscape = $customEscape; | |
$this->charset = $charset; | |
return $this->writeCode($tree, $name); | |
} | |
/** | |
* Helper function for walking the Mustache token parse tree. | |
* | |
* @throws InvalidArgumentException upon encountering unknown token types. | |
* | |
* @param array $tree Parse tree of Mustache tokens | |
* @param int $level (default: 0) | |
* | |
* @return string Generated PHP source code | |
*/ | |
private function walk(array $tree, $level = 0) | |
{ | |
$code = ''; | |
$level++; | |
foreach ($tree as $node) { | |
switch ($node[Mustache_Tokenizer::TYPE]) { | |
case Mustache_Tokenizer::T_SECTION: | |
$code .= $this->section( | |
$node[Mustache_Tokenizer::NODES], | |
$node[Mustache_Tokenizer::NAME], | |
$node[Mustache_Tokenizer::INDEX], | |
$node[Mustache_Tokenizer::END], | |
$node[Mustache_Tokenizer::OTAG], | |
$node[Mustache_Tokenizer::CTAG], | |
$level | |
); | |
break; | |
case Mustache_Tokenizer::T_INVERTED: | |
$code .= $this->invertedSection( | |
$node[Mustache_Tokenizer::NODES], | |
$node[Mustache_Tokenizer::NAME], | |
$level | |
); | |
break; | |
case Mustache_Tokenizer::T_PARTIAL: | |
case Mustache_Tokenizer::T_PARTIAL_2: | |
$code .= $this->partial( | |
$node[Mustache_Tokenizer::NAME], | |
isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '', | |
$level | |
); | |
break; | |
case Mustache_Tokenizer::T_UNESCAPED: | |
case Mustache_Tokenizer::T_UNESCAPED_2: | |
$code .= $this->variable($node[Mustache_Tokenizer::NAME], false, $level); | |
break; | |
case Mustache_Tokenizer::T_COMMENT: | |
break; | |
case Mustache_Tokenizer::T_ESCAPED: | |
$code .= $this->variable($node[Mustache_Tokenizer::NAME], true, $level); | |
break; | |
case Mustache_Tokenizer::T_TEXT: | |
$code .= $this->text($node[Mustache_Tokenizer::VALUE], $level); | |
break; | |
default: | |
throw new InvalidArgumentException('Unknown node type: '.json_encode($node)); | |
} | |
} | |
return $code; | |
} | |
const KLASS = '<?php | |
class %s extends Mustache_Template | |
{ | |
public function renderInternal(Mustache_Context $context, $indent = \'\', $escape = false) | |
{ | |
$buffer = \'\'; | |
%s | |
if ($escape) { | |
return %s; | |
} else { | |
return $buffer; | |
} | |
} | |
%s | |
}'; | |
/** | |
* Generate Mustache Template class PHP source. | |
* | |
* @param array $tree Parse tree of Mustache tokens | |
* @param string $name Mustache Template class name | |
* | |
* @return string Generated PHP source code | |
*/ | |
private function writeCode($tree, $name) | |
{ | |
$code = $this->walk($tree); | |
$sections = implode("\n", $this->sections); | |
return sprintf($this->prepare(self::KLASS, 0, false), $name, $code, $this->getEscape('$buffer'), $sections); | |
} | |
const SECTION_CALL = ' | |
// %s section | |
$buffer .= $this->section%s($context, $indent, $context->%s(%s)); | |
'; | |
const SECTION = ' | |
private function section%s(Mustache_Context $context, $indent, $value) { | |
$buffer = \'\'; | |
if (!is_string($value) && is_callable($value)) { | |
$source = %s; | |
$buffer .= $this->mustache | |
->loadLambda((string) call_user_func($value, $source)%s) | |
->renderInternal($context, $indent); | |
} elseif (!empty($value)) { | |
$values = $this->isIterable($value) ? $value : array($value); | |
foreach ($values as $value) { | |
$context->push($value);%s | |
$context->pop(); | |
} | |
} | |
return $buffer; | |
}'; | |
/** | |
* Generate Mustache Template section PHP source. | |
* | |
* @param array $nodes Array of child tokens | |
* @param string $id Section name | |
* @param int $start Section start offset | |
* @param int $end Section end offset | |
* @param string $otag Current Mustache opening tag | |
* @param string $ctag Current Mustache closing tag | |
* @param int $level | |
* | |
* @return string Generated section PHP source code | |
*/ | |
private function section($nodes, $id, $start, $end, $otag, $ctag, $level) | |
{ | |
$method = $this->getFindMethod($id); | |
$id = var_export($id, true); | |
$source = var_export(substr($this->source, $start, $end - $start), true); | |
if ($otag !== '{{' || $ctag !== '}}') { | |
$delims = ', '.var_export(sprintf('{{= %s %s =}}', $otag, $ctag), true); | |
} else { | |
$delims = ''; | |
} | |
$key = ucfirst(md5($delims."\n".$source)); | |
if (!isset($this->sections[$key])) { | |
$this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $source, $delims, $this->walk($nodes, 2)); | |
} | |
return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $key, $method, $id); | |
} | |
const INVERTED_SECTION = ' | |
// %s inverted section | |
$value = $context->%s(%s); | |
if (empty($value)) { | |
%s | |
}'; | |
/** | |
* Generate Mustache Template inverted section PHP source. | |
* | |
* @param array $nodes Array of child tokens | |
* @param string $id Section name | |
* @param int $level | |
* | |
* @return string Generated inverted section PHP source code | |
*/ | |
private function invertedSection($nodes, $id, $level) | |
{ | |
$method = $this->getFindMethod($id); | |
$id = var_export($id, true); | |
return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $this->walk($nodes, $level)); | |
} | |
const PARTIAL = ' | |
if ($partial = $this->mustache->loadPartial(%s)) { | |
$buffer .= $partial->renderInternal($context, %s); | |
} | |
'; | |
/** | |
* Generate Mustache Template partial call PHP source. | |
* | |
* @param string $id Partial name | |
* @param string $indent Whitespace indent to apply to partial | |
* @param int $level | |
* | |
* @return string Generated partial call PHP source code | |
*/ | |
private function partial($id, $indent, $level) | |
{ | |
return sprintf( | |
$this->prepare(self::PARTIAL, $level), | |
var_export($id, true), | |
var_export($indent, true) | |
); | |
} | |
const VARIABLE = ' | |
$value = $context->%s(%s); | |
if (!is_string($value) && is_callable($value)) { | |
$value = $this->mustache | |
->loadLambda((string) call_user_func($value)) | |
->renderInternal($context, $indent); | |
} | |
$buffer .= %s%s; | |
'; | |
/** | |
* Generate Mustache Template variable interpolation PHP source. | |
* | |
* @param string $id Variable name | |
* @param boolean $escape Escape the variable value for output? | |
* @param int $level | |
* | |
* @return string Generated variable interpolation PHP source | |
*/ | |
private function variable($id, $escape, $level) | |
{ | |
$method = $this->getFindMethod($id); | |
$id = ($method !== 'last') ? var_export($id, true) : ''; | |
$value = $escape ? $this->getEscape() : '$value'; | |
return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $this->flushIndent(), $value); | |
} | |
const LINE = '$buffer .= "\n";'; | |
const TEXT = '$buffer .= %s%s;'; | |
/** | |
* Generate Mustache Template output Buffer call PHP source. | |
* | |
* @param string $text | |
* @param int $level | |
* | |
* @return string Generated output Buffer call PHP source | |
*/ | |
private function text($text, $level) | |
{ | |
if ($text === "\n") { | |
$this->indentNextLine = true; | |
return $this->prepare(self::LINE, $level); | |
} else { | |
return sprintf($this->prepare(self::TEXT, $level), $this->flushIndent(), var_export($text, true)); | |
} | |
} | |
/** | |
* Prepare PHP source code snippet for output. | |
* | |
* @param string $text | |
* @param int $bonus Additional indent level (default: 0) | |
* @param boolean $prependNewline Prepend a newline to the snippet? (default: true) | |
* | |
* @return string PHP source code snippet | |
*/ | |
private function prepare($text, $bonus = 0, $prependNewline = true) | |
{ | |
$text = ($prependNewline ? "\n" : '').trim($text); | |
if ($prependNewline) { | |
$bonus++; | |
} | |
return preg_replace("/\n( {8})?/", "\n".str_repeat(" ", $bonus * 4), $text); | |
} | |
const DEFAULT_ESCAPE = 'htmlspecialchars(%s, ENT_COMPAT, %s)'; | |
const CUSTOM_ESCAPE = 'call_user_func($this->mustache->getEscape(), %s)'; | |
/** | |
* Get the current escaper. | |
* | |
* @param string $value (default: '$value') | |
* | |
* @return string Either a custom callback, or an inline call to `htmlspecialchars` | |
*/ | |
private function getEscape($value = '$value') | |
{ | |
if ($this->customEscape) { | |
return sprintf(self::CUSTOM_ESCAPE, $value); | |
} else { | |
return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->charset, true)); | |
} | |
} | |
/** | |
* Select the appropriate Context `find` method for a given $id. | |
* | |
* The return value will be one of `find`, `findDot` or `last`. | |
* | |
* @see Mustache_Context::find | |
* @see Mustache_Context::findDot | |
* @see Mustache_Context::last | |
* | |
* @param string $id Variable name | |
* | |
* @return string `find` method name | |
*/ | |
private function getFindMethod($id) | |
{ | |
if ($id === '.') { | |
return 'last'; | |
} elseif (strpos($id, '.') === false) { | |
return 'find'; | |
} else { | |
return 'findDot'; | |
} | |
} | |
const LINE_INDENT = '$indent . '; | |
/** | |
* Get the current $indent prefix to write to the buffer. | |
* | |
* @return string "$indent . " or "" | |
*/ | |
private function flushIndent() | |
{ | |
if ($this->indentNextLine) { | |
$this->indentNextLine = false; | |
return self::LINE_INDENT; | |
} else { | |
return ''; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// load and eval n templates from memcache | |
if (count($argv) !== 3) { | |
throw new InvalidArgumentException('usage: php memcache.php numtemplates size'); | |
} | |
$numtemplates = $argv[1]; | |
$templatesize = $argv[2]; | |
$memcache = new Memcache; | |
$memcache->connect('localhost', 11211); | |
$start = microtime(true); | |
for ($i = 0; $i < $numtemplates; $i++) { | |
$name = './tmp/' . $templatesize . $i . '.php'; | |
$src = $memcache->get($name); | |
if ($src === false) { | |
$src = file_get_contents($name); | |
$memcache->set($name, $src); | |
} | |
eval(substr($src, 6)); | |
} | |
$end = microtime(true); | |
echo $end - $start . "\n"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// create n php templates, store them in memcache + filesystem. | |
if (count($argv) !== 3) { | |
throw new InvalidArgumentException('usage: php prepare.php numtemplates size'); | |
} | |
$numtemplates = $argv[1]; | |
$templatesize = $argv[2]; | |
$memcache = new Memcache; | |
$memcache->connect('localhost', 11211); | |
$template = file_get_contents($templatesize . '.tpl'); | |
for ($i = 0; $i < $numtemplates; $i++) { | |
$name = './tmp/' . $templatesize . $i . '.php'; | |
$src = str_replace('{{ NUMBER }}', $i, $template); | |
file_put_contents($name, $src); | |
$memcache->set($name, $src); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// compile results | |
if (count($argv) !== 3) { | |
throw new InvalidArgumentException('usage: php report.php numtemplates size'); | |
} | |
$numtemplates = $argv[1]; | |
$templatesize = $argv[2]; | |
$apc = explode("\n", file_get_contents('./tmp/apc.csv')); | |
$apctotal = 0; | |
foreach ($apc as $n) { | |
$apctotal += (float) $n; | |
} | |
$apcavg = $apctotal / count($apc); | |
$memcache = explode("\n", file_get_contents('./tmp/memcache.csv')); | |
$memcachetotal = 0; | |
foreach ($memcache as $n) { | |
$memcachetotal += (float) $n; | |
} | |
$memcacheavg = $memcachetotal / count($memcache); | |
echo $numtemplates . ',' . $templatesize . ',' . $apcavg . ',' . $memcacheavg . "\n"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
templatecount | templatesize | apctime | memcachetime | |
---|---|---|---|---|
1 | tiny | 8.0303712324662E-5 | 0.00018252025951039 | |
1 | small | 0.00013026324185458 | 0.0002536340193315 | |
1 | medium | 0.00036764144897461 | 0.00049463185397061 | |
1 | large | 0.00082102688876065 | 0.0010207783092152 | |
10 | tiny | 0.00036371837962757 | 0.0005387392911044 | |
10 | small | 0.00077421014959162 | 0.001088099046187 | |
10 | medium | 0.0028614347631281 | 0.0032596371390603 | |
10 | large | 0.0073021758686412 | 0.0086643262342973 | |
100 | tiny | 0.0032066865400834 | 0.0045115080746737 | |
100 | small | 0.0071180083534934 | 0.0089254596016622 | |
100 | medium | 0.027306426655162 | 0.030949917706576 | |
100 | large | 0.069675445556641 | 0.084022608670321 | |
1000 | tiny | 0.029750650579279 | 0.041871894489635 | |
1000 | small | 0.066356637261131 | 0.080950476906516 | |
1000 | medium | 0.27880271998319 | 0.31849692084573 | |
1000 | large | 0.77091696045615 | 0.90584007176487 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* This file is part of Mustache.php. | |
* | |
* (c) 2012 Justin Hileman | |
* | |
* For the full copyright and license information, please view the LICENSE | |
* file that was distributed with this source code. | |
*/ | |
/** | |
* Mustache class autoloader. | |
*/ | |
class Mustache_Autoloader{{ NUMBER }} | |
{ | |
private $baseDir; | |
/** | |
* Autoloader constructor. | |
* | |
* @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..') | |
*/ | |
public function __construct($baseDir = null) | |
{ | |
if ($baseDir === null) { | |
$this->baseDir = dirname(__FILE__).'/..'; | |
} else { | |
$this->baseDir = rtrim($baseDir, '/'); | |
} | |
} | |
/** | |
* Register a new instance as an SPL autoloader. | |
* | |
* @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..') | |
* | |
* @return Mustache_Autoloader Registered Autoloader instance | |
*/ | |
public static function register($baseDir = null) | |
{ | |
$loader = new self($baseDir); | |
spl_autoload_register(array($loader, 'autoload')); | |
return $loader; | |
} | |
/** | |
* Autoload Mustache classes. | |
* | |
* @param string $class | |
*/ | |
public function autoload($class) | |
{ | |
if ($class[0] === '\\') { | |
$class = substr($class, 1); | |
} | |
if (strpos($class, 'Mustache') !== 0) { | |
return; | |
} | |
$file = sprintf('%s/%s.php', $this->baseDir, str_replace('_', '/', $class)); | |
if (is_file($file)) { | |
require $file; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class EmptyClass{{ NUMBER }} {} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment