callback('save_something', 'node') + callback('my_function_here', 'arg1', 'arg2') * * this improves cachability tremendously. if the user access checks in _menu were encoded with these, * we could cache one menu tree for all roles. This is also towards the goal of caching as much of the * model / view building as possible. * */ function callback($function) { $args = func_get_args(); $callback = array_shift($args); $data[p] = array('type' => 'callback')); $data[$function . '-' . sizeof($args)] => array('callback' => $callback, 'arguments' => $args); return $data; } // simply calls the callback function, if it is one. Otherwise returns the object again. function trigger($object) { if (!is_array($object) && ($object[p]['type'] != 'callback')) { return $object; } else { foreach ($object as $key => $callback) { if ($key != p) { // arguments can also be callbacks, so this needs to be recursive. // I don't think this will become too much of a bitch, but i fear the // day people start trying to nest callbacks 10 deep. foreach ($callback['arguments'] as $argument) { $callback['arguments'] = trigger($argument); } if ($callback['callback'] == 'drupal_get_context') { } if (isset($callback['callback'])) { $results[] = call_user_func_array($callback['callback'], $callback['arguments']); } } } } if (sizeof($results)) { return $results[0]; } else { // optionally configure ways to merge the results. the most logical being an &. return $results; } } /** * This is a special callback, and it's provided in this format for convenience. * it is used to populate the values of the object, in the case of submissions, * the context is a union of the (trimmed) _POST, the loaded data (if any) , * and the default_values (if any). * usage (will be) 'value' => context('objecttype/field/field'); * * all fields get a sensible context value set for them, automatically. but these * can be overridden. This saves a hell of a lot of typing, and the default case is * mostly what we need. */ function context($path) { return callback('drupal_get_context', $path); } // these feel like a bit of a hack right now. I'd prefer to look at how // we do context more completely. perhaps even have the trigger function // have a $context parameter that can be passed to it. it would make for // a cleaner code flow (i think). function drupal_get_context($path) { $data = drupal_set_context(); return $data[$path]; } function &drupal_set_context($object = null) { static $stored_object; if (!is_null($object)) { $stored_object = $object; } return $stored_object; } /** * Constructors. These follow the same basic pattern, in that they roll together several sets of defaults, * and save an ungodly amount of typing, additionally they construct the [_properties_] array, and provide * a simple way to merge in your own custom overrides. */ // sets the defaults, the field type and the overrides. function drupal_field($type, $arguments = array()) { $field = array(); $function = 'field_' . $type; if (function_exists($function)) { $field = $function(); } $data[p] = $arguments + $field + array('type' => 'field', 'field_type' => $type, 'view_widget' => 'text', 'edit_widget' => 'textfield'); return $data; } // sets the defaults, the field type and the overrides. function drupal_widget($field, $arguments = array()) { if (!is_array($field)) { $widget = $field; } else { $widget = $field[p][current_view_type() . '_widget']; } // an additional widget_ function could be here, to provide defaults. but hey. it's just a demo $field[p]['widget'] = $widget; $field[p] = $arguments + (array) $field[p]; return $field; } /** * CRUD functions. Each of these functions are used to automatically load/add/edit/delete the object. * Because the functions are consistent, they can be automatically called where applicable. * * Practically, this is just renaming a lot of functions we already have, and making them internally consistent. * Additionally, just the way that drupal_render can work without theme functions, I aim to allow scaffolding to * be for any of these basic functions that are defined. Which means the moment you have a model, and you have a * save function, you will be able to create the thing (both in the back end, and via the interface). * The moment you have a delete function, it can do a confirm delete dialog. etc. * also, all forms can have an automatic preview created for them. */ function load_node() { // could be an id, but not needed in this case. $data = variable_get('fapi_crud_test', array()); return $data; } function load_node_from_array($array) { // this is just an example. Basically, it's a clean way for load('node') to implement the array('nid' => $nid, 'uid' => 'something') // mechanism. // This will get even more important in the future, when we have things like 'file' models, and image models, where the image module // would have a load_file_from_image($image). Meaning that any action that works on files (move , copy, delete etc), would also be // applicable to images. // Check out apple automator to see where I am going with this. } function save_node($data) { // this is really just short hand. it should be happening in save(), which follows the same pattern as model() and view() $data[p]['type'] = 'data'; $data[p]['data_type'] = 'node'; variable_set('fapi_crud_test', $data); return $data; } // very often this could be the same function as the save handler. there will be a _models hook which allows you to specify that. function update_node($data) { return save_node($data); } function delete_node() { // could be an id, but not needed in this case variable_set('fapi_crud_test', array()); } /** * How to pull a model. This is where all the AOP magic happens, ie: model_alter etc. * the goal is to be able to cache the structure created after this, so we never have * to jump through all the hoops to be able to validate or generate a thing of this model * again. */ function model($type) { $function = 'model_' . $type; if (function_exists($function)) { $model = $function(); } // now we do the model_alter workflow thing. $model[p]['type'] = 'model'; $model[p]['model_type'] = $type; //this needs to be recursive. but this is just a demo. foreach ($model as $key => $field) { if ($key != p) { $model[$key][p]['name'] = isset($model[$key][p]['name']) ? $model[$key][p]['name'] : $key; $model[$key][p]['value'] = isset($model[$key][p]['value']) ? $model[$key][p]['value'] : context($key); $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. } } return $model; } /** * This lets the render function know which of the compatible default fields to use. * each field type provides defaults, that can be extended. Keep in mind that these view * types are entirely fluid. You could build 'export' widgets for all the elements, to * export to csv or xml. * also, the developer can choose to use different default fields for their fields if they choose. */ function current_view_type($type = null) { static $stored_type; if (!is_null($type)) { $stored_type = $type; } return $stored_type; } /** * This could also be handled by hook_fields or similar (like we have _elements), but i think i prefer making * just defining functions as useful as possible. This means we don't have to build large arrays with defaults. * * I will be using the same way the _forms hook works now, in that it tries the function call, else it tries to * check the _fields hook. This keeps the data set small and manageable. */ function field_title() { return array('edit_widget' => 'textfield', 'view_widget' => 'my_markup', 'filter' => 'strong'); } function field_content() { return array('edit_widget' => 'textfield', 'view_widget' => 'my_markup', 'filter' => 'soft'); } function field_email() { return array('edit_widget' => 'textfield', 'view_widget' => 'email', 'validate' => drupal_callback('valid_email')); } function filter_summary($string, $length) { return substr($string, 0, $length); } // just a simple theme function. I just made this since i didn't want to jump into drupal core and edit files yet. function theme_my_markup($field) { return $field[p]['name'] . ' : ' . $field[p]['value'] . "\n"; } // ditto =) if (!function_exists('theme')) { function theme($widget) { $args = func_get_args(); $widget = array_shift($args); $function = 'theme_' . $widget; return call_user_func_array($function, $args); } } // this is actually our happy fun friend drupal_render/form_render. // very simplified though, and not recursive. But that's not too hard. function render($view) { foreach ($view as $key => $item) { foreach ($item[p] as $key => $value) { $item[p][$key] = trigger($value); } if ($key != p) { // don't try to render the properties. $content .= theme($item[p]['widget'], $item); } } return $content; } // Almost exactly the same as model(), but this one accepts data too. // This is where the _POST would probably get into the form. (forms are a type of view) // The context data will end up being the data argument + object values + object defaults. function view($viewid, &$data) { current_view_type('view'); // just hard coded for now. // this can do lots of smart things, like automatically loading the node. etc. // but i will leave that for the crud stuff. $parts = explode('_', $viewid); $model_type = array_shift($parts); $data[p]['model_type'] = $model_type; $data[p]['view_type'] = 'view'; drupal_set_context($data); // needs to be merged over defaults. // same pattern emerges with save(); $model = model($model_type); $function = 'view_' . $viewid; if (function_exists($function)) { $view = $function($model); } $view[p] = array('type' => 'view', 'view_type' => 'view') + (array) $view[p]; return $view; } /// AND BELOW IS THE ACTUAL CODE PEOPLE WOULD WRITE. /** * Basic Model. * These are the fields you want in your object. Especially important is validation and filtering. * this will become a centralised mechanism for validating your object with, in both the front end * and the back end. */ function model_node() { $model['title'] = drupal_field('title'); $model['content'] = drupal_field('content'); $model['author'] = drupal_field('title'); // should have been : drupal_field('author'); // which triggers valid user validation and uses format_name to display the data. Same with date types. // What's more interesting however, is when we get into relationship api territory. IE: // $model['author'] = drupal_has_one('user'); .. and then that can be rendered through whatever user widgets // you can come up with. Like the profile block, or just the name, or whatever. I would also like to see us // go to this for the file api. Which i believe will be incredibly beneficial for us. return $model; } function view_node_summary($model) { $view['title'] = drupal_widget($model['title']); $view['content'] = drupal_widget($model['content']); // when it's doing recursive rendering, you will also be doing : // $view['group'] = drupal_widget('fieldset', array('title' => 'my title here', 'description' => 'my description')); // and then just : // $view['group']['fieldname'] = drupal_widget($model['fieldname']); // I would like to see helper functions like : drupal_table, and a mechanism of transposing tables too. return $view; } function view_node($model) { $view = $model; // this code will eventually be in a function called drupal_widgets, which allows you to // populate the view with multiple widgets at a time. This will probably be called by default if you don't specify // a view. foreach ($model as $key => $field) { if ($key != p) { $view[$key] = drupal_widget($model[$key]); } } return $view; } // just testing that it all still runs $data['title'] = 'my title goes here'; $data['content'] = 'my content goes here'; $data['author'] = 'Adrian'; print "SUMMARY \n"; print render(view('node_summary', $data)); print "FULL \n"; print render(view('node', $data));