/[drupal]/contributions/sandbox/adrian/fapi3_test.php
ViewVC logotype

Contents of /contributions/sandbox/adrian/fapi3_test.php

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (show annotations) (download) (as text)
Fri Sep 22 19:33:20 2006 UTC (3 years, 2 months ago) by adrian
Branch: MAIN
CVS Tags: HEAD
File MIME type: text/x-php
adding some of the test code that i presented at the forms api talk at DrupalCon.
1 <?php
2 define('p', '_properties_');
3
4 /**
5 * Callback constructor.
6 * It's just a formalised mechanism to handle fapi's existing callback structure,
7 * but it's also been built to allow you to have any fapi propterty be a callback.
8 *
9 * what's neat about this is that the following syntax works like a bomb.
10 * 'submit' => callback('save_something', 'node') + callback('my_function_here', 'arg1', 'arg2')
11 *
12 * this improves cachability tremendously. if the user access checks in _menu were encoded with these,
13 * we could cache one menu tree for all roles. This is also towards the goal of caching as much of the
14 * model / view building as possible.
15 *
16 */
17 function callback($function) {
18 $args = func_get_args();
19 $callback = array_shift($args);
20 $data[p] = array('type' => 'callback'));
21 $data[$function . '-' . sizeof($args)] => array('callback' => $callback, 'arguments' => $args);
22 return $data;
23 }
24
25 // simply calls the callback function, if it is one. Otherwise returns the object again.
26 function trigger($object) {
27 if (!is_array($object) && ($object[p]['type'] != 'callback')) {
28 return $object;
29 }
30 else {
31 foreach ($object as $key => $callback) {
32 if ($key != p) {
33 // arguments can also be callbacks, so this needs to be recursive.
34 // I don't think this will become too much of a bitch, but i fear the
35 // day people start trying to nest callbacks 10 deep.
36 foreach ($callback['arguments'] as $argument) {
37 $callback['arguments'] = trigger($argument);
38 }
39 if ($callback['callback'] == 'drupal_get_context') {
40
41 }
42 if (isset($callback['callback'])) {
43 $results[] = call_user_func_array($callback['callback'], $callback['arguments']);
44 }
45 }
46 }
47 }
48 if (sizeof($results)) {
49 return $results[0];
50 }
51 else {
52 // optionally configure ways to merge the results. the most logical being an &.
53 return $results;
54 }
55 }
56
57 /**
58 * This is a special callback, and it's provided in this format for convenience.
59 * it is used to populate the values of the object, in the case of submissions,
60 * the context is a union of the (trimmed) _POST, the loaded data (if any) ,
61 * and the default_values (if any).
62 * usage (will be) 'value' => context('objecttype/field/field');
63 *
64 * all fields get a sensible context value set for them, automatically. but these
65 * can be overridden. This saves a hell of a lot of typing, and the default case is
66 * mostly what we need.
67 */
68 function context($path) {
69 return callback('drupal_get_context', $path);
70 }
71
72
73 // these feel like a bit of a hack right now. I'd prefer to look at how
74 // we do context more completely. perhaps even have the trigger function
75 // have a $context parameter that can be passed to it. it would make for
76 // a cleaner code flow (i think).
77 function drupal_get_context($path) {
78 $data = drupal_set_context();
79 return $data[$path];
80 }
81
82 function &drupal_set_context($object = null) {
83 static $stored_object;
84 if (!is_null($object)) {
85 $stored_object = $object;
86 }
87 return $stored_object;
88 }
89
90 /**
91 * Constructors. These follow the same basic pattern, in that they roll together several sets of defaults,
92 * and save an ungodly amount of typing, additionally they construct the [_properties_] array, and provide
93 * a simple way to merge in your own custom overrides.
94 */
95
96 // sets the defaults, the field type and the overrides.
97 function drupal_field($type, $arguments = array()) {
98 $field = array();
99 $function = 'field_' . $type;
100 if (function_exists($function)) {
101 $field = $function();
102 }
103 $data[p] = $arguments + $field +
104 array('type' => 'field', 'field_type' => $type,
105 'view_widget' => 'text', 'edit_widget' => 'textfield');
106 return $data;
107 }
108
109 // sets the defaults, the field type and the overrides.
110 function drupal_widget($field, $arguments = array()) {
111 if (!is_array($field)) {
112 $widget = $field;
113 }
114 else {
115 $widget = $field[p][current_view_type() . '_widget'];
116 }
117
118 // an additional widget_ function could be here, to provide defaults. but hey. it's just a demo
119
120 $field[p]['widget'] = $widget;
121 $field[p] = $arguments + (array) $field[p];
122 return $field;
123 }
124
125
126 /**
127 * CRUD functions. Each of these functions are used to automatically load/add/edit/delete the object.
128 * Because the functions are consistent, they can be automatically called where applicable.
129 *
130 * Practically, this is just renaming a lot of functions we already have, and making them internally consistent.
131 * Additionally, just the way that drupal_render can work without theme functions, I aim to allow scaffolding to
132 * be for any of these basic functions that are defined. Which means the moment you have a model, and you have a
133 * save function, you will be able to create the thing (both in the back end, and via the interface).
134 * The moment you have a delete function, it can do a confirm delete dialog. etc.
135 * also, all forms can have an automatic preview created for them.
136 */
137 function load_node() { // could be an id, but not needed in this case.
138 $data = variable_get('fapi_crud_test', array());
139 return $data;
140 }
141
142 function load_node_from_array($array) {
143 // this is just an example. Basically, it's a clean way for load('node') to implement the array('nid' => $nid, 'uid' => 'something')
144 // mechanism.
145
146 // This will get even more important in the future, when we have things like 'file' models, and image models, where the image module
147 // would have a load_file_from_image($image). Meaning that any action that works on files (move , copy, delete etc), would also be
148 // applicable to images.
149
150 // Check out apple automator to see where I am going with this.
151 }
152
153 function save_node($data) {
154 // this is really just short hand. it should be happening in save(), which follows the same pattern as model() and view()
155 $data[p]['type'] = 'data';
156 $data[p]['data_type'] = 'node';
157 variable_set('fapi_crud_test', $data);
158 return $data;
159 }
160
161 // very often this could be the same function as the save handler. there will be a _models hook which allows you to specify that.
162 function update_node($data) {
163 return save_node($data);
164 }
165
166 function delete_node() { // could be an id, but not needed in this case
167 variable_set('fapi_crud_test', array());
168 }
169
170
171 /**
172 * How to pull a model. This is where all the AOP magic happens, ie: model_alter etc.
173 * the goal is to be able to cache the structure created after this, so we never have
174 * to jump through all the hoops to be able to validate or generate a thing of this model
175 * again.
176 */
177 function model($type) {
178 $function = 'model_' . $type;
179 if (function_exists($function)) {
180 $model = $function();
181 }
182 // now we do the model_alter workflow thing.
183
184 $model[p]['type'] = 'model';
185 $model[p]['model_type'] = $type;
186
187 //this needs to be recursive. but this is just a demo.
188 foreach ($model as $key => $field) {
189 if ($key != p) {
190 $model[$key][p]['name'] = isset($model[$key][p]['name']) ? $model[$key][p]['name'] : $key;
191 $model[$key][p]['value'] = isset($model[$key][p]['value']) ? $model[$key][p]['value'] : context($key);
192 $model[$key][p]['title'] = isset($model[$key][p]['title']) ? $model[$key][p]['title'] : $key; // this should be run through t(), but i am developing this outside of drupal.
193 }
194 }
195 return $model;
196 }
197
198 /**
199 * This lets the render function know which of the compatible default fields to use.
200 * each field type provides defaults, that can be extended. Keep in mind that these view
201 * types are entirely fluid. You could build 'export' widgets for all the elements, to
202 * export to csv or xml.
203 * also, the developer can choose to use different default fields for their fields if they choose.
204 */
205 function current_view_type($type = null) {
206 static $stored_type;
207 if (!is_null($type)) {
208 $stored_type = $type;
209 }
210 return $stored_type;
211 }
212
213 /**
214 * This could also be handled by hook_fields or similar (like we have _elements), but i think i prefer making
215 * just defining functions as useful as possible. This means we don't have to build large arrays with defaults.
216 *
217 * I will be using the same way the _forms hook works now, in that it tries the function call, else it tries to
218 * check the _fields hook. This keeps the data set small and manageable.
219 */
220 function field_title() {
221 return array('edit_widget' => 'textfield', 'view_widget' => 'my_markup', 'filter' => 'strong');
222 }
223
224 function field_content() {
225 return array('edit_widget' => 'textfield', 'view_widget' => 'my_markup', 'filter' => 'soft');
226 }
227
228 function field_email() {
229 return array('edit_widget' => 'textfield', 'view_widget' => 'email', 'validate' => drupal_callback('valid_email'));
230 }
231
232 function filter_summary($string, $length) {
233 return substr($string, 0, $length);
234 }
235
236
237 // just a simple theme function. I just made this since i didn't want to jump into drupal core and edit files yet.
238 function theme_my_markup($field) {
239 return $field[p]['name'] . ' : ' . $field[p]['value'] . "\n";
240 }
241
242 // ditto =)
243 if (!function_exists('theme')) {
244 function theme($widget) {
245 $args = func_get_args();
246 $widget = array_shift($args);
247
248 $function = 'theme_' . $widget;
249 return call_user_func_array($function, $args);
250 }
251 }
252
253 // this is actually our happy fun friend drupal_render/form_render.
254 // very simplified though, and not recursive. But that's not too hard.
255 function render($view) {
256 foreach ($view as $key => $item) {
257 foreach ($item[p] as $key => $value) {
258 $item[p][$key] = trigger($value);
259 }
260 if ($key != p) { // don't try to render the properties.
261 $content .= theme($item[p]['widget'], $item);
262 }
263
264 }
265 return $content;
266 }
267
268 // Almost exactly the same as model(), but this one accepts data too.
269 // This is where the _POST would probably get into the form. (forms are a type of view)
270 // The context data will end up being the data argument + object values + object defaults.
271 function view($viewid, &$data) {
272 current_view_type('view'); // just hard coded for now.
273 // this can do lots of smart things, like automatically loading the node. etc.
274 // but i will leave that for the crud stuff.
275 $parts = explode('_', $viewid);
276 $model_type = array_shift($parts);
277
278 $data[p]['model_type'] = $model_type;
279 $data[p]['view_type'] = 'view';
280 drupal_set_context($data); // needs to be merged over defaults.
281 // same pattern emerges with save();
282
283 $model = model($model_type);
284 $function = 'view_' . $viewid;
285 if (function_exists($function)) {
286 $view = $function($model);
287 }
288 $view[p] = array('type' => 'view', 'view_type' => 'view') + (array) $view[p];
289
290 return $view;
291 }
292
293
294 /// AND BELOW IS THE ACTUAL CODE PEOPLE WOULD WRITE.
295
296 /**
297 * Basic Model.
298 * These are the fields you want in your object. Especially important is validation and filtering.
299 * this will become a centralised mechanism for validating your object with, in both the front end
300 * and the back end.
301 */
302 function model_node() {
303 $model['title'] = drupal_field('title');
304 $model['content'] = drupal_field('content');
305 $model['author'] = drupal_field('title'); // should have been : drupal_field('author');
306 // which triggers valid user validation and uses format_name to display the data. Same with date types.
307 // What's more interesting however, is when we get into relationship api territory. IE:
308 // $model['author'] = drupal_has_one('user'); .. and then that can be rendered through whatever user widgets
309 // you can come up with. Like the profile block, or just the name, or whatever. I would also like to see us
310 // go to this for the file api. Which i believe will be incredibly beneficial for us.
311 return $model;
312 }
313
314
315 function view_node_summary($model) {
316 $view['title'] = drupal_widget($model['title']);
317 $view['content'] = drupal_widget($model['content']);
318 // when it's doing recursive rendering, you will also be doing :
319 // $view['group'] = drupal_widget('fieldset', array('title' => 'my title here', 'description' => 'my description'));
320 // and then just :
321 // $view['group']['fieldname'] = drupal_widget($model['fieldname']);
322 // I would like to see helper functions like : drupal_table, and a mechanism of transposing tables too.
323 return $view;
324 }
325
326 function view_node($model) {
327 $view = $model;
328 // this code will eventually be in a function called drupal_widgets, which allows you to
329 // populate the view with multiple widgets at a time. This will probably be called by default if you don't specify
330 // a view.
331 foreach ($model as $key => $field) {
332 if ($key != p) {
333 $view[$key] = drupal_widget($model[$key]);
334 }
335 }
336 return $view;
337 }
338
339 // just testing that it all still runs
340
341 $data['title'] = 'my title goes here';
342 $data['content'] = 'my content goes here';
343 $data['author'] = 'Adrian';
344
345 print "SUMMARY \n";
346 print render(view('node_summary', $data));
347
348 print "FULL \n";
349 print render(view('node', $data));
350

  ViewVC Help
Powered by ViewVC 1.1.2