I'll try to provide some background to what drove me to start writing this Drupal module, what is the intended use, some example scenarios, as well as some ideas for medium and longer term development. Some brief implementation details are provided to both illustrate the ideas and spur discussion. I've explored these ideas from a somewhat different angle in the rigid and the states and metadata writeups.
This pamphlet serves the dual purpose of describing binder and tickle the vanity of the author, me that is. And I do like random writing.
These ideas were brewing for some time.I'll skip the long term personal interests and start with some of the frustrations I have, when using Drupal. OK, I am using it and I do prefer it to quite a few other web based CMS, since I see it as a flexible tool with potential. Actually I quite like the fact that it does not know what it is, it makes it better. The frustrations. If I want to cook some new content, page, whatever, and I need something special I need to start coding. Yes, but I'm lazy. If the functionality is already there, but not the way I like it or need it at the moment, to spend days coding is frustrating. Having to code things for doing trivial things to please the editor of a website or a manager is frustrating, especially when it is obvious that if it was pen and paper they could solve such a problem - they are not stupid, they are just afraid of coding. This significantly reduces the fun in what I am doing. I can spend my time far better watching East-enders than being frustrated by repetitive trivial variations of an obvious theme. Thinking about it, writing code sounds better.
react_on_signal(X): foreach(thing) { foreach(action) { if guards(X) then { action(X) } } } guards(X): foreach(guard) { $result &&= guard(X) }
Never mind. Me bing lazy leads to me dreaming up some, hopefully, interesting and useful schemes, which are definitely experimental, but can prove efficient. I always wanted to try out something along the lines of Abstract State Machines, but never had the time and the chance. I like the fluidity which they introduce and the fact that you are restricted by their apparent simplicity, but in fact all you need is imagination. The scheme I came up in binder is a variation of this idea, which admittedly is more limited, but more manageable, so a bit more suitable in my opinion for the Drupal context. I don't want to try proving equivalence or anything, I'll leave it to the big brains, if they are interested. A description of this model can be found in this other writing, more precisely I will be talking mainly about take 2.
If frustrations with the flexibility and trivial extensibility are not enough, let's mention a few others. Let's take for example semantic structures. What do I mean by this close to nonsense collection of buzzwords? Something like Docbook in drupal for example. Yes, there is the book module, which goes a good way towards organising nodes into structures, but think about a combination of book, docbook and a variation of the htmleditor. It will be userfriendly, nearly WYSIWYG(M), auto-magically splittable and/or mergeable. To explain what I am dreaming better, I mean a structured collection in line with docbook, a user friendly XML editor degrading gracefully into a standard plain text editor, a filter splitting this collection into smaller components and placing it into the DB but preserving the structure. Having in mind that the modern browsers do support XML with stylesheets, DOM, etc it should be possible. What if I want to be able to choose between a single, two and three column display for a node? Don't worry, I'm not smoking anything at the moment.
Another dream comes from a few emails exchanged some time ago on drupal-dev. What if anything which has a date can be turned into an event. An event actually has a few properties - start time, maybe end time, can be repetitive, can have a location, can have quite a few other properties. How would you do that in Drupal now? Use flexinode? Use nodeapi? The first is invariable, so you might end up with multiple different event types, the second is indiscriminative - applies to every node, so you will need to provide for some mechanism to flag up which nodes are events, ... What if we want to define a task? We will go through a similar set of decisions and later coding. What if we want a milestone in a plan? There we go again. Milestone, which turns into an event under some conditions?
So there is no easy way, at the moment to easily extend preset things in Drupal, at an arbitrary point of their life cycle. If we want to do some behaviour or presentation, which is behaviour after all, change we need to program it explicitly. If it is for a hobby for a programmer then fine, but if it is for my son's website, come on it is unreasonable to expect a six year old to do PHP coding. He is not good in writing, yet.
All this leads to binder. It is mixing the ideas of flexinode for flexible content construction, the nodeapi for object extension and adds individual node extension to achieve a new cocktail. These three features are a good platform to build on to create flexible content management. They are not sufficient and may end up in tedious procedures like every time you need to create an particular event type you will need to extend your base type, but there are possible remedies, unfortunately I am not going to expand in that direction right now. My main aim is to form a clean and simple platform to start building on. I can see a lot of merit not only in binder, but in the approaches taken by flexinode and workflow, leading to CCK. In my opinion they compliment each other, or putting it in posh lingo they are orthogonal. They solve different problems related to flexible content creation and lifecycle management. And let's not avoid to remember the ideas of metadata Drupal as well.
Binder implements a simple of a Virtual Machine(VM) a naive variation of Abstract State Machines. It is based around the idea that an object or node is a collection of components (things and fields will be used here as alternatives). Each component knows how to handle the different events like view, edit, verify, whatever. The composition of the states of all these components is the state of the object. When an object receives an event, the actions defined for this event are executed if they are allowed by their guards. The VM can add, remove and rename the components.
Why do I insist to calling this a Virtual Machine? The reason is dual. First, it does put the mind process into the direction of programming languages, with the side effect of confusing a lot of people. Second, the implementation relies on a collection of interrelated in memory structures and operations, and the term tends to naturally summarise it.
The meta operations, or operations to modify the state (the structure) of an object as described above are add, remove and rename a field. Extra, there is a mechanism to pass events, extract the fields from the DB.
The heart of th VM is the binder_nodepi() function. It handles all the usual events, by calling the guarded functions for this of the fields responsible for them. It implements the aforementioned approach. From the diagramme it is obvious that it uses binder_get_fields to do its dirty work to load the schema either from DB or directly from the binder_fields(), which is basically a simple wrapper function, returning as a reference the needed $fields structure.
binder_page_fields_list() implements the user interface for displaying the node structure related to binder, and for remove or rename a field. Nothing really unusual is done. binder_change_schema is used to despatch the calls for rename,delete, or add. The smart bit is in enabling mass renames, deletes and adds by passing an array of fields for which an operation should be performed.
The arrangements are simple. There is a structure called $field. This is an array indexed by node id - $nid. For each node all fields' information is stored in arrays indexed by field id $fid. For each field the label, the data, and the actions for each event ($op)are stored. The data could be anything, the decision is on the field. The $fields[$nid][$fid][$op][actions][][guards][] is an array of all the guard of that particular action. $fields[$nid][$fid][$op][actions][][call] is a reference to the actions function. As you can see this is a hierarchy, which naturally reflects the VM's logic. Additionally a reference to $fields[$nid]is stored in the $node, it comes very useful for minimising a lot of operations. Similar techniques will be employed in the future to satisfy different scenarios. binder_fields() is the 'owner' of this structure. In theory techniques like the 'Singleton Pattern' can be employed for better safety, but I don't think it is nessesary at the moment, since only binder_nodeapi() is intended for use from outside binder.
Binder is responsible to maintain it's state. This means that it needs to maintain only its schema reflected properly in DB. The fields' data is their own responsibility. It is done via bindapi calls. The state is updated via binder_change() calls.
Due to the fragmented nature of the beast, wrapping adds and removes in transactions is nessesary, if we want to maintain the database consistency. This is done in binder_schema_*() functions.
There are a few traps, following the computational model. The first deals with a case of classic non-determinism, so I have to expand a bit, an put a single programming rule to tackle it. No field operations should not update other fields data. That is essential, because otherwise we can't guarantee or predict the end result. You may have noticed that an assumption is made that all reactions to an event are made in an atomic time - instantly and the same time. It's just the VM is implemented sequentially. This means that we should not be able to guarantee the execution order of the actions or guards. This means as well that the guards and actions can't see the result of the other actions during state execution. These assumptions lead to the above rule. Field module writers need to have a very good discipline
This does not mean that the fields cannot trigger meta-operations. Yes, they can do that and it is safe. Rename is safe, by it's own nature. It manipulates a convenience label. Remove is safe, since any modifications to that field will end up in the big bucket. Add is safe, since the new member of the family is not present and thus will not be executed in the state, yet. In theory the model can be updated to an extend to make the above rule weaker, but that means complex checks, and a some form of concurrency control, etc... Is the price worth the pain?
Binder makes possible to have composite nodes. What if we have one and the same field being part of more that one node. This means that we can have several meanings of add and delete. Delete is easier to streamline - any delete is an operation to remove the field instance from the node. If there are no more instances in db, then we remove the field. This does not pose danger to deleting embedded nodes, since only the node adaptor field gets the treatment. Add has two different meanings add new and add an existing field. At this point the intent is to implement add new, leaving the add existing case to be resolved in the future. I'm not sure about the UI. The bottom line is I can't see how to implement a single add operation, so probably two will have to be implemented.
Order between fields should not be relied on, except for views. Views are operations that don't change the state.
Composite nodes lead to another problem. It comes from nodes containing as fields other existing nodes as well. Recursive nodes can be eliminated, including the cases of deep mutual recursion by parsing the metadata on add. Binder, in theory, should be able to handle wrapping menu callback pages, not immediately, because core needs to be updated. Binder implements a simple, but powerful programming language. This leads to some very complex UI problems. Binder operations are node operations, as such they should belong to node edit. As such they might compromise the consistency of the drupal UI.
There will be a need for very good documentation, since a lot of power is given to the user. The good and bad patterns and idioms should be explained, otherwise it will be explained.
I've written this as an aid to help code and test the binder implementation. It does not store anything in DB. As such it is a useless, hapless not a number but placeholder. It will become a number soon. Text, textarea and the rest ala flexinode should be simple to implement.
Wraps existing nodes. Edit field displays an offers an add node textfield or browse to add. If you choose browsing, then while you browse on possible to add pages you get a 'special' button. Clicking on that button places the appropriate $nid into the schema.
The idea is that a field tracks the state, state events, etc... and performs operations like add, delete, rename. Remember we can't change other fields' state. Doesn't this sound like a workflow? Tying taxonomy to label a state leads to numerous possibilities. Just play on.