-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathtessera.php
211 lines (199 loc) · 6.53 KB
/
tessera.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
<?php
/**
* Tessera, another minimalist PHP framework
* @author Justin Poliey <jdp34@njit.edu>
* @copyright 2009 Justin Poliey <jdp34@njit.edu>
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
* @package Tessera
*/
error_reporting(E_ALL);
/**
* Tessera, a minimalist PHP framework
*/
class Tessera {
/**
* Compiled regular expression routes that match requests to methods
* @var array
* @access protected
*/
protected $routes;
/**
* Local variables set by {@link Tessera::set} accessible to views and layouts
* @var array
* @access protected
*/
protected $locals = array();
/**
* Generate clean or messy URLs
* @var boolean
* @access private
*/
protected $clean_urls = false;
/**
* Creates a Tessera application
* @param array $routes Array of routes matched to method names
* @param array $config Configuration values
*/
function __construct($routes, $config = array()) {
$this->config = $config;
$this->errors = array();
$this->request_method = $_SERVER['REQUEST_METHOD'];
/* Snag the query string and use it as the request path */
if (isset($_SERVER['REDIRECT_QUERY_STRING'])) {
$_SERVER['QUERY_STRING'] = $_SERVER['REDIRECT_QUERY_STRING'];
}
$this->request_path = $_SERVER['QUERY_STRING'];
/* Set a default request path if necessary */
if (strlen($this->request_path) == 0) {
$this->request_path = '/';
}
/* Compile all routes, select one, and respond */
$this->routes = $this->compileRoutes($routes);
if (!$this->routeRequest($this->request_path, $this->routes)) {
$this->action = '__error';
$this->params = array('code' => 404);
}
$this->respond($this->action);
}
/**
* Returns a nicely formatted platform-independent path. Takes a variable number of arguments, each being part of a path
* @return string
*/
private function path_join() {
return join(func_get_args(), DIRECTORY_SEPARATOR);
}
/**
* Makes a variable available to views and templates
* @param string $local The name of the variable
* @param mixed $value The value of the variable
*/
protected function set($local, $value) {
if (is_string($local) && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $local)) {
$this->locals[$local] = $value;
}
}
/**
* Compiles Tessera routes into regular expressions
* @param array $routes List of Tessera routes
*/
private function compileRoutes($routes) {
$compiled_routes = array();
/* Replace each route with a regular expression (if it isn't already) */
foreach ($routes as $pattern => $action) {
if (substr($pattern, 0, 1) == '^') {
$compiled_routes[] = array(
'pattern' => str_replace('/', '\\/', $pattern),
'action' => $action,
);
continue;
}
$compiled = array(
'pattern' => '^' . preg_quote($pattern, '/'),
'action' => $action
);
/* Replace all named params with a regular expression match */
preg_match_all('/\/\$(\w+)/i', $pattern, $named_params);
foreach ($named_params[1] as $i => $v) {
$compiled['pattern'] = str_replace("/\\\${$v}", "/(?P<{$v}>[\w\-]+)", $compiled['pattern']);
$compiled['params'][$i] = $v;
}
/* Replace all splat params with a regular expression match */
$compiled['pattern'] = preg_replace('/\\\\\*\\\\\*/', "(.*)", $compiled['pattern']);
$compiled['pattern'] = preg_replace('/\\\\\*([^*]?)/', "([^\/]*)$1", $compiled['pattern']);
/* Add the compiled pattern to the list of routes */
$compiled_routes[] = $compiled;
}
return $compiled_routes;
}
/**
* Routes a request to a Tessera route, and calls the related action
* @param string $request_path The URL being requested
* @param array $routes The compiled Tessera routes to be matched against
* @return boolean
*/
private function routeRequest($request_path, $routes) {
foreach($routes as $id => $route) {
$final_pattern = "/{$route['pattern']}(?:\/)?$/i";
if (preg_match($final_pattern, $request_path, $raw)) {
$this->params = array();
$this->splat = array();
reset($raw); // Reset array iterator
next($raw); // Skip the whole URL match
while (list($key, $value) = each($raw)) {
$this->params[] = $value;
if (is_string($key)) {
$this->params[$key] = $value;
next($raw);
}
else {
$this->splat[] = $value;
}
}
$this->action = $route['action'];
return true;
}
}
return false;
}
/**
* Responds to a matched Tessera route
* @param string $action The name of the action being called
*/
private function respond($action) {
/* By default, there is no layout and view loads from <action>.html */
if (!isset($this->view)) {
$this->view = $action;
}
/* Make sure the action is callable and not a Tessera internal */
$protected_actions = array('path_join', 'set', 'compileRoutes', 'routeRequest', 'respond', 'render');
if (!is_callable(array($this, $action)) || in_array($action, $protected_actions)) {
trigger_error("Unhandled not found request to <strong>{$this->request_path}</strong>", E_USER_ERROR);
}
/* Call the action and snag its output */
$this->locals = array();
if (is_callable(array($this, '__before'))) {
call_user_func(array($this, '__before'));
}
ob_start();
/* Send named params as function arguments, for backward compatibility */
$positionals = array();
foreach ($this->params as $name => $value) {
if (is_string($name)) {
$positionals[] = $value;
}
}
call_user_func_array(array($this, $action), $positionals);
$this->script_output = ob_get_clean();
/* Load and execute the view file if it exists. Otherwise its value is the script output */
$view_html = $this->render($this->view, false);
$this->view_output = $view_html ? $view_html : $this->script_output;
/* Load, execute, and display the layout file. If it can't, display the view output */
if (isset($this->layout)) {
$this->layout_output = $this->render($this->layout);
}
echo isset($this->layout_output) ? $this->layout_output : $this->view_output;
}
/**
* Renders a view and returns its HTML representation
* @param string $view The name of the view
* @param boolean $force Force the file to exist
* @return string
*/
protected function render($view, $force = true) {
$view_file = $this->path_join('views', $view . '.html');
if (!is_file($view_file)) {
if ($force) {
trigger_error("View file <strong>{$view_file}</strong> associated with <strong>{$this->action}</strong> not found", E_USER_ERROR);
}
else {
return null;
}
}
extract($this->locals);
ob_start();
include $view_file;
$html = ob_get_clean();
return $html;
}
}
?>