The main problem is highly related to the view object itself, which has grown organically. The main goal of this discussion is to separate concerns from the view object (reduce its capabilities) to creates an API that is loosely coupled (that is, 3 interfaces that deal with: Configuration, Data Source and Rendering). An anecdotal evidence is that this object is known to be the heart of the system.
Do you remember the original black-and-white movie The Blob? Perhaps you saw only the recent remake. In either case, the story line was almost the same: A drip-sized, jellylike alien life form from outer space somehow makes it to Earth.
Whenever the jelly thing eats (usually unsuspecting earthlings), it grows. Meanwhile, incredulous earthlings panic and ignore the one crazy scientist who knows what’s happening. Many more people are eaten before they come to their senses. Eventually, the Blob grows so large that it threatens to wipe out the entire planet.
The movie is a good analogy for the View class, which consumes entire object-oriented architectures.
Figure 1 The Blob.
This is characterized by a class diagram composed of a single complex controller class sourrounded by other data classes, as shown in figure 2. The key problem here is that the majority of the responsibilities are allocated to this class. Thus making the "API" completely unusable.
Here we have a software module that is given responsibilities that overlap most other parts of the system for system control or system management. The View class contains the majority of the process, and the other objects contain the data. Architectures with the Blob have separated process from data; in other words, they are procedural-style rather than object-oriented architectures.
Figure 2 View Class.
A procedural design separates process from data, whereas an object-oriented design merges process and data models, along with partitions. The allocation of responisibilities is not repartitioned during system evolution, so that one module becomes predominant. The is often accompanied by unecessary code, making it hard to differentiate between the useful functionality of the View Class and no-longer-used code.
Some funny facts:
- render method. The doc says: "Render this view for a certain display. Note: You should better use just the preview function if you want to render a view."
- execute_display method. "To be called externally by whatever mechanism invokes the view, such as a page callback, hook_block, etc. [...] This function should NOT be used by anything external as this returns data in the format specified by the display. [...]". - WTF ?!
- Private/protected methods are not really privates, they are public with an underscore as prefix to the function name.
- There is a mix-and-match of camelCase functions and php_procedural_style function name.
- An evil copy method.
- A clone_view method having a sudden clarity about itself: "Because views are complicated objects within objects [...]" - mhhh
- There is a very curious fix_missing_relationships method that would need some love/reconsideration.
- The View::views_object_types() make me think of the speech by David Zuelke Designing HTTP interfaces and RESTful Web Services. Especially the slide 77 about the Twitter API: DELETE api.twitter.com/l/status/destroy/12345.json. "DELETE destroy", RPC much ?
The View class can (but is not limited to): determine a user access to the view, run attachment displays for the view, build an individual set of handlers, build a query for the view, force the view to build a title, clean the house, buy you a beer, choose a display among those accessible to the user, delete - save - update the view from the database, use ajax, integrates pagers, makes nice hamburgers..., etc
Views can do a lot more than that, but those are some of the obvious uses of Views. You guessed it, a lot of things... It is by the way clearly stated on the project page.
- Single class with a large number of attributes (49), operations (67), OUCH!
- A disparate collection of unrelated attributes and operations encapsulated in a single class. An overall lack of cohesiveness of the attributes and operations.
- A single controller class with associated simple, data-object classes. Views expert I havn't yet digged into plugins and handlers so please correct me on this point.
- An absence of object-oriented design. The single controller class often nearly encapsulates the applications entire functionality, much like a procedural main program.
- A migrated legacy design that has not been properly refactored to an object-oriented architecture.
- The View Class is typically too complex for reuse and testing. It may be inefficient and expensive to load into memory, using excessive resources, even for simple operations.
I mentionned a complete rewritting from the ground-up, but hopefully the solution involves a form of refactoring. The key is to move behavior away from the View class. It may be appropriate to reallocate behavior to some of the encapsulated data objects in a way that makes these objects more capable and the View class less complex.
- Identify or categorize related attributes and operations according to contracts. These contracts should be cohesive in that they all directly relate to a common focus, behavior, or function within the overall system. Therefore, the first step is to identify cohesive sets of operations and attributes that represent contracts. In this case, we could gather operations related to query management like, the db_table, base_table, base_field attributes along with the build, _build methods. The CRUD operation to make the View persistent can also be grouped together. We could also identity all operations and attributes related to individual display objects, such as argument, field, sort, filters, etc. And finally all UI related attributes and methods.
- The second step is to look for "natural homes" for these contract based collections of functionality and then migrate them there. In this example, we gather operations related to query management and migrate them from the VIEW class and move them to the QUERYBUILDER class, as shown in Figure 3. We do the same with CRUD operations and attributes related to persistance and exportable to the VIEWENTITY class. This both simplifies the VIEW class and makes the QUERYBUILDER and VIEWENTITY classes more than simple encapsulated data tables. The result is a better object-oriented design.
- The third step is to remove all "far-coupled", or redundant, indirect associations. In the View class, the display handler is initially far-coupled to the VIEW class in that each display handler really belongs to a display ARRAY, which in turn belongs to a VIEW.
- Next, where appropriate, we migrate associates to derived classes to a common base class. In the example, once the far-coupling has been removed between the VIEW and DISPLAY classes, we need to migrate DISPLAYs to a separate class. I need some View experts here to define a strategy, because it seems to be partly done with plugins ?
- Finally, we remove all transient associations, replacing them as appropriate with type specifiers to attributes and operations arguments. In our example, an EXPORT or a GET_ITEMS_PER_PAGE would be a transient process, and could be moved into a separate transient class with locale attributes that establish the specified export plugin or pager criteria for a specific instance of an export or a paged display.
Figure 3 Regrouping the Blob by contracts.
While I described general practises along with some examples that may be erroneous, it really need some reviews and a Views expert architectural viewpoint. Also for the sake of conventions, please use a camel case approach for naming functions. I propose in the discussion to establish how to explode the View class, then creates a set of interfaces (which will defines the API) and will be implemented by the re-architectured components.
Having a high-level architecture will let us create interfaces that will represents the final API, while highly-skilled components implementers will be able to work in a loosely coupled fashion on each components. The whole thing will be more unit testable and I am sure we will enjoy to work programatically with Views.