forked from nervetattoo/aether
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Aether.php
318 lines (291 loc) · 10.9 KB
/
Aether.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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
<?php // vim:set ts=4 sw=4 et:
// Default to only smarty support for now
// The autoload fails to handle this because its smarty naming
/**
* The Aether web framework
*
* Aether is a rule driven modularized web framework for PHP.
* Instead of following a more traditional MVC pattern
* it helps you connect a resource (an url) to a set of modules
* that each provide a part of the page
*
* A "module" can be thought of as a Controller, except you can, and will,
* have multiple of them for each page as normaly a page requires
* functionality that doesnt logicaly connect to just one component.
* Viewing an article? Then you probably need links to more articles,
* maybe some forum integration, maybe a gallery etc etc
* All these things should in Aether be handled as separate modules
* instead of trying to jam it into one controller.
*
* Instead of bringing in a huge package of thousands of thousands of lines of code
* containing everything from the framework, to models (orm) to templating to helpers
* Aether focusing merely on the issue of delegating urls/resources to code
* and in their communication in between.
*
*
* Created: 2007-01-31
* @author Raymond Julin
* @package aether
*/
class Aether {
/**
* Hold service locator
* @var AetherServiceLocator
*/
private $sl = null;
/**
* Section
* @var AetherSection
*/
private $section = null;
/**
* Root folder for this project
* @var string
*/
private $projectRoot;
/**
* Module manager
* The ModuleManager holds a mapping over all modules in the
* project and offers some functionality for working with them
* @var AetherModuleManager
*/
private $moduleManager;
public static $aetherPath;
/**
* Start Aether.
* On start it will parse the projects configuration file,
* it will try to match the presented http request to a rule
* in the project configuration and create some overview
* over which modules it will need to render once
* a request to render them comes
*
* @access public
* @return Aether
* @param string $configPath Optional path to the configuration file for the project
*/
public function __construct($configPath=false) {
self::$aetherPath = pathinfo(__FILE__, PATHINFO_DIRNAME) . "/";
spl_autoload_register(array('Aether', 'autoLoad'));
$this->sl = new AetherServiceLocator;
$this->sl->set('aetherPath', self::$aetherPath);
// Initiate all required helper objects
$parsedUrl = new AetherUrlParser;
$parsedUrl->parseServerArray($_SERVER);
$this->sl->set('parsedUrl', $parsedUrl);
// Set autoloader
// TODO Make this more uesable
/**
* Find config folder for project
* By convention the config folder is always placed at
* $project/config, while using getcwd() MUST return the
* $project/www/ folder
*/
$projectPath = preg_replace("/www\/?$/", "", getcwd());
$this->sl->set("projectRoot", $projectPath);
$paths = array(
$configPath,
$projectPath . 'config/autogenerated.config.xml',
$projectPath . 'config/aether.config.xml'
);
foreach ($paths as $configPath) {
if (file_exists($configPath))
break;
}
try {
$config = new AetherConfig($configPath);
$config->matchUrl($parsedUrl);
$this->sl->set('aetherConfig', $config);
}
catch (AetherMissingFileException $e) {
/**
* This means that someone forgot to ensure the config
* file actually exists
*/
$msg = "No configuration file for project found: " . $e->getMessage();
throw new Exception($msg);
}
catch (AetherNoUrlRuleMatchException $e) {
/**
* This means parsing of configuration file failed
* by the simple fact that no rules matches
* the url. This is due to a bad developer
*/
$msg = "No rule matched url in config file: " . $e->getMessage();
throw new Exception($msg);
}
/**
* Set up module manager and run the start() stage
*/
$this->moduleManager = new AetherModuleManager($this->sl);
$this->moduleManager->start();
$options = $config->getOptions();
if (array_key_exists("cache", $options) && $options['cache'] == 'on') {
if (isset($options['cacheClass']) && isset($options['cacheOptions'])) {
$cache = $this->getCacheObject($options['cacheClass'], $options['cacheOptions']);
$this->sl->set("cache", $cache);
}
}
/**
* Make sure base and root for this request is stored
* in the service locator so it can be made available
* to the magical $aether array in templates
*/
$magic = $this->sl->getVector('templateGlobals');
$magic['base'] = $config->getBase();
$magic['root'] = $config->getRoot();
$magic['urlVars'] = $config->getUrlVars();
$magic['runningMode'] = $options['AetherRunningMode'];
$magic['requestUri'] = $_SERVER['REQUEST_URI'];
$magic['domain'] = $_SERVER['SERVER_NAME'];
if (isset($_SERVER['HTTP_REFERER']))
$magic['referer'] = $_SERVER['HTTP_REFERER'];
if ($_SERVER['SERVER_PORT'] != 80)
$magic['domain'] .= ":" . $_SERVER['SERVER_PORT'];
$magic['options'] = $options;
/**
* If we are in TEST mode we should prepare a timer object
* and time everything that happens
*/
if ($options['AetherRunningMode'] == 'test') {
// Prepare timer
$timer = new AetherTimer;
$timer->start('aether_main');
$this->sl->set('timer', $timer);
}
/**
* Start session if session switch is turned on in
* configuration file
*/
if (array_key_exists('session', $options) AND $options['session'] == 'on')
session_start();
// Initiate section
try {
$searchPath = (isset($options['searchpath']))
? $options['searchpath'] : $projectPath;
AetherSectionFactory::$path = $searchPath;
$this->section = AetherSectionFactory::create(
$config->getSection(),
$this->sl
);
$this->sl->set('section', $this->section);
if (isset($timer))
$timer->tick('aether_main', 'section_initiate');
}
catch (Exception $e) {
// Failed to load section, what to do?
throw new Exception('Failed horribly: ' . $e->getMessage());
}
}
/**
* Ask the AetherSection to render itself,
* or if a service is requested it will try to load that service
*
* @access public
* @return string
*/
public function render() {
/**
* If a service is requested simply render the service
*/
if (isset($_GET['module']) && isset($_GET['service'])) {
$response = $this->section->service(
$_GET['module'], $_GET['service']);
if (!is_object($response) || !($response instanceof AetherResponse)) {
trigger_error("Expected " . preg_replace("/[^A-z0-9]+/", "", $_GET['module']) . "::service() to return an AetherResponse object", E_USER_WARNING);
}
else {
$response->draw($this->sl);
}
}
else if (isset($_GET['_esi'])) {
/**
* ESI support and rendering of only one module by provider name
* # _esi to list
* # _esi=<providerName> to render one module with settings of the url path
*/
$config = $this->sl->get('aetherConfig');
if (strlen($_GET['_esi']) > 0) {
$this->section->renderProviderWithCacheHeaders($_GET['_esi']);
}
else {
$modules = $config->getModules();
$providers = array();
foreach ($modules as $m) {
$providers[] = array(
'provides' => $m['provides'],
'cache' => isset($m['cache']) ? $m['cache'] : false
);
}
$response = new AetherJSONResponse(array('providers' => $providers));
$response->draw($this->sl);
}
}
else {
$response = $this->section->response();
$response->draw($this->sl);
/**
* Run stop stage of modules
*/
$this->moduleManager->stop();
}
}
/**
* Used to autoload aether classes
*
* @access public
* @return bool
*/
public static function autoLoad($class) {
if (class_exists($class, false))
return true;
if ($class == "Smarty")
require_once(self::$aetherPath . 'lib/templating/smarty/libs/Smarty.class.php');
// Split up the name of the class by camel case (AetherDriver
$matches = preg_split('/([A-Z][^A-Z]+)/', $class, -1,
PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
if (empty($matches) || $matches[0] != 'Aether')
return false;
// Find the class location
switch ($matches[1]) {
case 'Template':
$path = self::$aetherPath . 'lib/templating/';
break;
default:
$path = self::$aetherPath . 'lib/';
break;
}
$i = 0;
foreach ($matches as $match) {
// Turn the rest of the array into a string that can be used as a filename
$filenameArray = array_slice($matches, $i);
$filename = implode('', $filenameArray);
// Check if there is a file with this name.
// Files have precendence over folders
$filename = $path . $filename . '.php';
if (file_exists($filename)) {
$filePath = $filename;
break;
}
// If there is a directory with this name add it to the dir path
$match = strtolower($match);
if (file_exists($path . $match))
$path = $path . $match . '/';
else
break;
$i++;
}
if (isset($filePath) && !empty($filePath)) {
require $filePath;
return true;
}
return false;
}
private function getCacheObject($class, $options) {
if (class_exists($class)) {
$obj = new $class($options);
if ($obj instanceof AetherCacheInterface)
return $obj;
}
return false;
}
}