/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
<?php

/**
 * Fight: 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
 *
 * FIGHT! ...brings the following changes to Flight:
 *
 *  1. Global $app object.
 *
 *  2. App directorties.
 *
 * 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 specify either "special cases" of actions, or actions for a
 * lower level of the URL path, by using a double-underscore to represent a path
 * separator in your action method name.  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 contracred to a
 * single underscore (so that the path separators can be represented as a
 * double-underscode in method names)
 */

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

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

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

// 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' ) ) {
							$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 ) ) {
								$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 view parameters
$app->view()->set( 'app', $app );