/www/slight

To get this branch, use:
bzr branch http://bzr.ed.am/www/slight
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
<?php

/**
 * Slight: an extension to Flight, in the form of useful configuration changes
 * and extra library code.
 *
 * @copyright Copyright (c) 2015, Tim Marston <tim@ed.am>
 * @licence   MIT
 *
 * Slight brings the following changes to Flight:
 *
 *  1. A global $app object.
 *
 *  2. App directories.
 *
 * It is expected that your app has the following layout, and class and view
 * paths are set up to reflect this:
 *
 *     /
 *     +-- index.php          (see note below)
 *     +-- app/
 *         +-- controllers/
 *         +-- models/
 *         +-- views/
 *
 * Note that index.php MUST go in the top-level directory, or autoloading
 * breaks.  If you want to keep it in the app/ directory, add a shim index.php
 * that just requires app/bootstrap.php (or whatever you want to call it).
 *
 *  3. The route() method now accepts a class name and routes to class methods.
 *
 * The flight route() method now accepts a class name in an array (so, like a
 * callable, except with no method specified) which indicates a class in which
 * the part of the URL beyond the route specified should be looked up as a
 * method and additional URL parts beyond those represented by the name of the
 * method passed as arguments to the method.
 *
 * For example, you could configure routing to a class's methods like this:
 *
 *     $app->route( '/some/path', array( 'MyClass' ) );
 *
 * Then, for example, /some/path/list will route to MyClass::action_list(),
 * assuming it exists.
 *
 * If MyClass::default_action() exists, then /some/path will route to it.  If
 * you don't require default functionality, you might still want to use this to
 * call some other action method.
 *
 * If other URL parts are specified beyond the name of the matched method, for
 * example /some/path/list/1/2, then these are passed as arguments to the
 * action.  You would define the action method as follows:
 *
 *     public static function action_list( $foo = 0, $bar = 0 )
 *
 * NOTE: the use of default values for arguments is REQUIRED, or a PHP error
 * would occur where if they were missing in the HTTP request.
 *
 * You can also write "specialisations" of actions, where an action has a
 * specific argument value.  This is achieved by using a double underscore in
 * the method name to represent the path separator.  For example, using the
 * above routing, /some/path/list/1 will route to MyClass::action_list__1(), if
 * it exists, leaving URLs that have other values in the last part of their path
 * (for example, /some/path/list/2) to still route to MyClass::action_list() as
 * before (passing 2 as the first argument).
 *
 * Also note that hyphens in URLs are converted to underscores (or they wouldn't
 * be usable as method names), and multiple underscores are contracted to a
 * single underscore (so that the path separators can be represented as a
 * double-underscore in method names)
 *
 *  4. Add database connection and classes
 *
 * The new method connect() takes an array of connection parameters, as follows:
 *     type: the database type (defaults to mysql)
 *     host: the database hostname (defaults to localhost)
 *     port: database port number (optional)
 *     dbname: database name
 *     username: authentication user
 *     password: authentication password
 *     prefix: a table prefix
 *
 * This makes a PDO connection available at $app->db().
 *
 * A Model base class is available, for an app to base it's own models on.
 *
 *  5. Helper functions
 *
 * The following helper functions are provided:
 *
 * e()
 *     This is a global function which escapes text for use in HTML.
 * $app->link( 'some/where' )
 *     Constructs and returns links, taking in to account the base_url.
 */


// instantiate global flight app
require 'flight/autoload.php';
$app = new flight\Engine();

// set slight class path
$app->path( __DIR__.'/classes' );

// set app paths
$app->set( 'flight.views.path', 'app/views' );
$app->path( 'app/controllers' );
$app->path( 'app/models' );

// config
$app->set( 'flight.log_errors', true );

// add class method router advice to route() method
$app->before( 'route',
	function( &$params, &$output ) use( &$app )
	{
		// is the callback an array containing only a classname?
		if( count( $params ) >= 2 &&
			is_array( $params[ 1 ] ) &&
			count( $params[ 1 ] ) == 1 )
		{
			$class = $params[ 1 ][ 0 ];

			// fix-up pattern
			$params[ 0 ] = preg_replace( '/\/$/', '', $params[ 0 ] ).'/*';

			// replace callback with method router
			$params[ 1 ] =
				function( $route ) use( &$app, &$class )
				{
					// clean up splat
					$splat = preg_replace(
						array( '/(.)\/$/', '/-/', '/_+/' ),
						array( '\1', '_', '_' ), $route->splat );

					// default action?
					if( $splat === '' ) {
						if( method_exists( $class, 'default_action' ) )
						{
							// set controller url
							$app->set( 'controller', '' );

							// instantiate class and call method
							$obj = new $class();
							return $obj->default_action();
						}
					}
					else {
						// method parts
						$parts = explode( '/', $splat );

						// find method
						$params = array();
						while( count( $parts ) )
						{
							// check to see if combined parts make a method name
							$method = 'action_'.implode( '__', $parts );
							if( method_exists( $class, $method ) )
							{
								// set controller url
								$controller = explode( '/', preg_replace(
									array( '/^\//', '/\/$/' ), '',
									$app->request()->url ) );
								array_splice( $controller, -count( $params ) );
								$app->set( 'controller',
									join( '/', $controller ) );

								// instantiate class and call method
								$obj = new $class();
								return call_user_func_array(
									array( $obj, $method ), $params );
							}

							// discard last part as a param and keep looking
							array_unshift( $params, array_pop( $parts ) );
						}
					}

					// 404
					$app->notFound();
				};

			// pass route to callback
			$params[ 2 ] = true;
		}
	} );

// add $app to global view parameters
$app->view()->set( 'app', $app );

// add connect() method
$app->map( 'connect',
	function( $params ) use( &$app )
	{
		// default values
		$params += array( 'type' => 'mysql', 'host' => 'localhost' );

		// construct pdo string
		$pdostr = $params[ 'type' ].':host='.$params[ 'host' ];
		if( isset( $params[ 'port' ] ) ) $pdostr .= ';port='.$params[ 'port' ];
		$pdostr .= ';dbname='.$params[ 'dbname' ];

		// register DB class
		$app->register( 'db', 'PDO',
			array( $pdostr, $params[ 'username' ], $params[ 'password' ] ) );

		// set variables
		if( isset( $params[ 'prefix' ] ) ) {
			$app->set( 'dbprefix', $params[ 'prefix' ] );
		}
	} );

// add general html escaping function
function e( $text )
{
	return htmlspecialchars( $text, ENT_COMPAT | ENT_HTML5 );
}

// add link helper
$app->map( 'link',
	function( $url = null ) use( &$app )
	{
		if( is_null( $url ) ) $url = $app->get( 'controller' );
		$url = preg_replace( array( '/^\//', '/\/$/' ), '', $url );
		return $app->get( 'base_url' ).'/'.$url;
	} );