Skip to content

Instantly share code, notes, and snippets.

@bayleedev
Last active August 29, 2015 13:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bayleedev/10970807 to your computer and use it in GitHub Desktop.
Save bayleedev/10970807 to your computer and use it in GitHub Desktop.
<?php
class LayeredTemplate {
/**
* The methods defined in this class.
*
* We want to determine if class B (extends A)
* has method C, but not if class A has it.
*
* @var array
*/
public static $definedBlocks = array();
/**
* Determines if a block method exists for a given class.
* It does not count if defined within a parent.
*
* @param string $name Name of the block you are searching for.
* @return bool
*/
public static function blockExists($name) {
return in_array($name, static::$definedBlocks);
}
/**
* Iterates the class tree starting from `$class`, finds all that have the `$block`
* defined within them, not a parent.
*
* @param string $class Class to start with.
* @param string $block Block method you are searching for.
* @return array
*/
public static function topTwoClassesWithBlock($class, $block) {
$classes = array();
do {
if ($class::blockExists($block)) {
$classes[] = $class;
}
} while ($class = get_parent_class($class));
return array_slice($classes, 0, 2);
}
/**
* Given a list of classes, will statically call all methods `$block` and return their values.
*
* This calls them "statically" but if within this class structure calls it as an instance
* method using PHP magic.
*
* @param array $classes Classes you want to render the block on.
* @param string $block Block method you want to render.
* @return array
*/
public function renderBlocksOnClasses($classes, $block) {
$output = array();
foreach ($classes as $class) {
$output[] = $class::$block();
}
return $output;
}
/**
* Renders a given block.
*
* This is necessary to support `{:child}` in themes.
*
* @param string $blockName Block you wish to process.
*/
public function renderBlock($blockName) {
$classes = static::topTwoClassesWithBlock(get_called_class(), $blockName);
$output = $this->renderBlocksOnClasses($classes, $blockName);
if (empty($output[1]) || strpos($output[1], '{:child}') === false) {
return $output[0];
}
return str_replace('{:child}', $output[0], $output[1]);
}
}
class Theme extends LayeredTemplate {
public static $definedBlocks = array('main');
public function main() {
?><html><head><title><?=$this->title();?></title></head><body><?=$this->body();?></body></html><?php
}
}
class ChildTheme extends Theme {
public static $definedBlocks = array('title');
public function title() {
return 'foo {:child}';
}
}
class Guides extends ChildTheme {
public static $definedBlocks = array();
}
class Acne extends Guides {
public static $definedBlocks = array('title');
public function title() {
return 'bar';
}
}
class AcnePage2 extends Acne {
public static $definedBlocks = array();
}
$foo = new AcnePage2();
echo $foo->title() . PHP_EOL; // => "bar"
echo $foo->renderBlock('title') . PHP_EOL; // => "foo bar"
require 'pry'
class Template
def main
"<html><head><title><?=$this->title();?></title></head><body>#{body}</body></html>"
end
def two_top_klasses(method)
klasses = []
klasses << self.class.instance_method(method).owner
klasses << klasses[0].ancestors[1].instance_method(method).owner
klasses
rescue
klasses
end
def get_klass_method_outputs(method)
[].tap do |out|
two_top_klasses(method).each do |klass|
out.unshift(klass.instance_method(method).bind(self).call)
end
end
end
def render(method)
out = get_klass_method_outputs(method)
out.shift.gsub!('{:child}', out.shift)
end
end
class ChildTheme < Template
def title
"foo {:child}"
end
end
class Guides < ChildTheme
end
class Acne < Guides
def title
"bar"
end
end
class AcnePage2 < Acne
end
AcnePage2.new.title # => "bar"
p AcnePage2.new.render(:title) # => "foo bar"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment