Last active
August 29, 2015 13:59
-
-
Save bayleedev/10970807 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 | |
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" |
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
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