Don't print OK if a test fails
[hoplite.git] / README
1 hoplite A lightweight MVC framework
2 ================================================================================
3
4 Hoplite is the successor framework to phalanx. While many of the ideas in
5 phalanx were good starting point explorations, it was ultimately over-
6 engineered. Hoplite aims to be a no-frills web framework--a starting point for
7 applications that can incorporate other technologies.
8
9 --------------------------------------------------------------------------------
10
11 Basic flow:
12
13 1. HTTP request is made.
14 2. Web server (Apache, Lighty, etc.) receive the request and route it, via
15 mod_rewrite or something similar to the entrypoint, which is typically
16 index.php.
17 3. RootController is initialized and Run with the $GLOBALS (_GET, _POST, etc.).
18 4. A HttpRequest object is created with the initializing data.
19 5. A UrlMap matches a URL prefix (with pathc component extraction) to an Action.
20 6. The Action produces a HttpResponse to the Request.
21 7. An OutputFilter examines the Request and Response to create the body of the
22 HTTP response.
23
24 --------------------------------------------------------------------------------
25
26 Actions:
27
28 The base Action class is abstract and has three states: before, acting, and
29 after:
30
31 Action {
32 __construct(RootController)
33
34 controller() -> RootController
35
36 FilterRequest(HttpRequest, HttpResponse)
37
38 Invoke(HttpRequest, HttpResponse)
39
40 FilterResponse(HttpResponse, HttpResponse)
41 }
42
43 The filter methods are available for two reasons:
44 1. To validate data in the request
45 2. To format data in the response
46
47 These hook points can also be used to implement interceptors, with the aid of
48 the RootController interface.
49
50 RootController {
51 __construct($GLOBALS)
52
53 Run()
54 Stop()
55
56 RouteRequest(string)
57 InvokeAction(Action)
58
59 LookupAction(string)
60 }
61
62 In the entrypoint, the RootController is Run(). This creates and owns the
63 Request and Response objects. At any point when an Action is executing, it can
64 invoke control-flow changing methods on the controller. Stop() will force the
65 OutputFilter to run and will terminate the program. InvokeAction() will replace
66 the current Action with the parameter. This will move the Action between its
67 states.
68
69 RouteRequest() is called by Run() to perform the evaluation and lookup of the
70 UrlMap. This may also be called to perform an internal redirect. Note that this
71 will reuse the existing Request and Response objects.
72
73 Finally LookupAction() performs a reverse-lookup of class name to URL patterns.
74
75 --------------------------------------------------------------------------------
76
77 Advanced actions:
78
79 The basic Action is abstract. For creating individual pages, this merely
80 overriding Invoke() can be used to fill the HttpResponse's body for simple HTML
81 output.
82
83 RestActions are a special kind of action, where Invoke() will further dispatch
84 the request based on the HTTP method. This makes creating RESTful web services
85 and AJAX applications easy:
86
87 RestAction : Action {
88 DoGet(HttpRequest, HttpResponse)
89 DoPost(...)
90 DoDelete(...)
91 DoPut(...)
92 }
93
94 Another type of action is an ActionController, which maps a URL path component
95 to a method in the typical MVC style. The ActionController will look at the
96 'action' key in the HttpRequest's data dictionary to determine which method to
97 invoke. For example, if you had this entry in the UrlMap:
98
99 'user/{action}/{id}' => 'UserActionController'
100
101 Then the UrlMap would extract the 'action' and 'id' paramters and add them to
102 the HttpRequest object. If 'action' were 'view', then it would invoke
103 ActionView().
104
105 --------------------------------------------------------------------------------
106
107 Output filtering:
108
109 The last stage of the request pipeline is output filtering. At this stage, the
110 HttpResponse is transformed into the body of the HTTP response.
111
112 If the HttpRequest has the X-Requested-With header, for example, the output
113 filter would know to respond with JSON. It could also look for other parameters
114 in the request (like ?format=xml) to determine the response encoding.
115
116 Template systems can hook in here, too. Actions can store arbitrary data in the
117 HttpResponse, including the name of a template in which to render the response
118 data. A simple templating system is included with Hoplite, but because the
119 model is represented entirely within the HttpResponse, other templating systems
120 can also be used freely.
121
122 --------------------------------------------------------------------------------
123
124 URL mapping:
125
126 There are two ways to map URLs to Actions. The first is prefix matching, which
127 assumes that all paths are relative to the URL of the RootController and do not
128 need to start with a slash. In this form, any substrings within {curly braces}
129 will be extracted as a variable by that name. The value will be placed in the
130 HttpRequest's data dictionary.
131
132 If the application were at:
133
134 https://example.com/webapp/index.php
135
136 Requests will be in the rewritten form of:
137
138 https://example.comm/webapp/user/view/42
139
140 The UrlMap could be any of the following:
141
142 ID paramter extracted manually from the URL or through GET or POST variables
143 'user/view'
144
145 Using path parameter extraction
146 'user/view/{id}'
147
148 Using an ActionController (see previous section)
149 'user/{action}'
150 Or,
151 'user/{action}/{id}'
152
153 In the above forms, the matcher does prefix matching. Combined with the fact
154 that rules are evaluated linearly, this can lead to a potentially unexpected
155 result. When you have a more general rule before a specific one, the general
156 will always be the one matched. For example:
157
158 'document/edit'
159 'document/edit/{id}'
160
161 The second rule will never be matched, because the evaluator will find and
162 invoke the first rule and then stop. This isn't always the desired behavior and
163 can be altered by adding two slashes to the end of a rule. These two slashes
164 make the parser matching strict equality, rather than prefix.
165
166 The second type of matching is done by regular expressions. These patterns both
167 start and end with a slash. They are evaluated every time a request is routed
168 and any pattern groups are saved in the url_pattern key of the HttpRequest data.
169
170 For example, the following could be used to match the above routes:
171
172 '/user\/([a-z]+)(\/([0-9]*))?/'