Completed
Push — master ( be6b9d...0e6890 )
by Tom
03:08
created

Builder   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 126
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Importance

Changes 28
Bugs 1 Features 1
Metric Value
wmc 26
c 28
b 1
f 1
lcom 1
cbo 15
dl 0
loc 126
rs 9.1667

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A setTime() 0 3 1
B output() 0 24 4
A doPostProcessing() 0 4 1
A executeTssRule() 0 6 2
A loadTemplate() 0 7 3
A getRules() 0 14 3
A getContentProperty() 0 12 2
A setCache() 0 3 1
A getLocale() 0 5 3
A registerProperty() 0 3 1
A registerFormatter() 0 3 1
A setLocale() 0 3 1
A isValidDoc() 0 3 2
1
<?php
2
/* @description     Transformation Style Sheets - Revolutionising PHP templating    *
3
 * @author          Tom Butler [email protected]                                             *
4
 * @copyright       2015 Tom Butler <[email protected]> | https://r.je/                      *
5
 * @license         http://www.opensource.org/licenses/bsd-license.php  BSD License *
6
 * @version         0.9                                                             */
7
namespace Transphporm;
8
/** Builds a Transphorm instance from the 3 constituent parts. XML template string, TSS string and data */
9
class Builder {
10
	private $template;
11
	private $tss;
12
	private $registeredProperties = [];
13
	private $formatters = [];
14
	private $locale;
15
	private $baseDir;
16
	private $cache;
17
	private $time;
18
19
	public function __construct($template, $tss = '') {
20
		$this->template = $template;
21
		$this->tss = $tss;
22
		$this->cache = new Cache(new \ArrayObject());
23
	}
24
25
	//Allow setting the time used by Transphporm for caching. This is for testing purposes
26
	//Would be better if PHP allowed setting the script clock, but this is the simplest way of overriding it
27
	public function setTime($time) {
28
		$this->time = $time;
29
	}
30
31
	public function output($data = null, $document = false) {
32
		$locale = $this->getLocale();
33
		$data = new Hook\DataFunction(new \SplObjectStorage(), $data, $locale, $this->baseDir);
34
		$headers = [];
35
		$this->registerProperty('content', $this->getContentProperty($data, $locale, $headers));
36
		$this->registerProperty('repeat', new Property\Repeat($data));
37
		$this->registerProperty('display', new Property\Display);
38
		$this->registerProperty('bind', new Property\Bind($data));
39
40
		$cachedOutput = $this->loadTemplate();
41
		//To be a valid XML document it must have a root element, automatically wrap it in <template> to ensure it does
42
		$template = new Template($this->isValidDoc($cachedOutput['body']) ? $cachedOutput['body'] : '<template>' . $cachedOutput['body'] . '</template>' );
43
44
		//Allow $time to be set via arguments to spoof time passage during tests
45
		foreach ($this->getRules($template) as $rule) {
46
			if ($rule->shouldRun($this->time)) $this->executeTssRule($rule, $template, $data);			
47
		}
48
		
49
		$result = ['body' => $template->output($document), 'headers' => array_merge($cachedOutput['headers'], $headers)];
50
		$this->cache->write($this->template, $result);		
51
		$result['body'] = $this->doPostProcessing($template)->output($document);
52
53
		return (object) $result;
54
	}
55
56
	//Add a postprocessing hook. This cleans up anything transphporm has added to the markup which needs to be removed
57
	private function doPostProcessing($template) {
58
		$template->addHook('//*[@transphporm]', new Hook\PostProcess());
59
		return $template;
60
	}
61
62
	//Process a TSS rule e.g. `ul li {content: "foo"; format: bar}
63
	private function executeTssRule($rule, $template, $data) {
64
		$rule->touch();
65
		$hook = new Hook\Rule($rule->properties, new Hook\PseudoMatcher($rule->pseudo, $data), $data);
66
		foreach ($this->registeredProperties as $name => $property) $hook->registerProperty($name, $property);
67
		$template->addHook($rule->query, $hook);
68
	}
69
70
	//Load a template, firstly check if it's a file or a valid string
71
	private function loadTemplate() {
72
		if (trim($this->template)[0] !== '<') {			
73
			$xml = $this->cache->load($this->template, filemtime($this->template));
74
			return $xml ? $xml : ['body' => file_get_contents($this->template), 'headers' => []];
75
		}
76
		else return ['body' => $this->template, 'headers' => []];	
77
	}
78
79
	//Load the TSS rules either from a file or as a string
80
	//N.b. only files can be cached
81
	private function getRules($template) {		
82
		if (is_file($this->tss)) {
83
			$this->baseDir = dirname(realpath($this->tss)) . DIRECTORY_SEPARATOR;
84
			//The cache for the key: the filename and template prefix
85
			//Each template may have a different prefix which changes the parsed TSS,
86
			//Because of this the cache needs to be generated for each template prefix.
87
			$key = $this->tss . $template->getPrefix() . $this->baseDir;
88
			//Try to load the cached rules, if not set in the cache (or expired) parse the supplied sheet
89
			$rules = $this->cache->load($key, filemtime($this->tss));
90
			if (!$rules) return $this->cache->write($key, (new Sheet(file_get_contents($this->tss), $this->baseDir, $template->getPrefix()))->parse());
91
			else return $rules;
92
		}
93
		else return (new Sheet($this->tss, $this->baseDir, $template->getPrefix()))->parse();
94
	}
95
96
	private function getContentProperty($data, $locale, &$headers) {
97
		$formatter = new Hook\Formatter();
98
		$formatter->register(new Formatter\Number($locale));
99
		$formatter->register(new Formatter\Date($locale));
100
		$formatter->register(new Formatter\StringFormatter());
101
		
102
		foreach ($this->formatters as $format) $formatter->register($format);
103
104
		$basicProperties = new Property\Content($data, $headers, $formatter);
105
106
		return $basicProperties;
107
	}
108
109
	public function setCache(\ArrayAccess $cache) {
110
		$this->cache = new Cache($cache);
111
	}
112
113
	private function getLocale() {
114
		if (is_array($this->locale)) return $this->locale;
115
		else if (strlen($this->locale) > 0) return json_decode(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'Formatter' . DIRECTORY_SEPARATOR . 'Locale' . DIRECTORY_SEPARATOR . $this->locale . '.json'), true);
116
		else return json_decode(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'Formatter' . DIRECTORY_SEPARATOR . 'Locale' . DIRECTORY_SEPARATOR . 'enGB.json'), true);
117
	}
118
119
	public function registerProperty($name, Property $property) {
120
		$this->registeredProperties[$name] = $property;
121
	}
122
123
	public function registerFormatter($formatter) {
124
		$this->formatters[] = $formatter;
125
	}
126
127
	public function setLocale($locale) {
128
		$this->locale = $locale;
129
	}
130
131
	private function isValidDoc($xml) {
132
		return strpos($xml, '<!') === 0 || strpos($xml, '<?') === 0;
133
	}
134
}
135