The benefits of breaking code into very small components which do one and only one simple function are obvious. But there is nothing which mandates that we should make each and every function a separate function in itself.
Consider the following case:
There is one big function, however EACH of the cases included in the function is isolated, and runs only by itself. All of the case blocks can be just copy/pasted into a different code body, wrapped in their function name. So its modular. There wont be any multiple cases(ifs) combined, ever.
All the small functions which reside in the case blocks use $vars array as their main variable. So any number of variables in any format can be passed to the parent iterator as part of an array. There are no limitations.
The parent iterator can be run anywhere, from any place, even within itself by requesting a particular action. ie $this->run_actions('close_ticket');
It has massive advantage regarding the common procedures which need to be run, and may need to be run over all actions requested. output buffering is one, and any action hooks, filters or any other all encompassing system that can be imagined.
This format allows any future new procedures which need be run before and after any action and/or on the inputs and outputs of any action, to be easily integrated. (For the particular case i have in my hands, the appearance of such cases in future is certain!!!.) If all these actions were divided into small functions instead, you would need to either go and change hooks and filters on each of the functions, or still have some sort of other function to dispatch these onto those small functions. With this format, you just place them before or after the switch/case block.
Code reading is simple: When you see a particular case, you know what it does -> 'view_tickets' is the ticket view action, and it takes $vars as an input.
Obviously, a truly hypermassive function will have various disadvantages.
So, the question is:
Assuming that the size of the function is kept at a reasonable size and the principles of modularity and one simple action per case is preserved, also considering that anyone who works with this code in future will not need to look into this code and must not modify this code and need to know only the hooks and filters which will be documented elsewhere than code itself, (including any variables they use) do you think this could be an efficient approach to combining tasks which need common procedures run on them?
public function run_actions($action,$vars=false)
{
// Global, common filters and procedures which all actions need or may need
ob_start();
switch($action)
{
case 'view_tickets':
{
// Do things
// Echo things if necessary
break;
}
case 'close_ticket':
{
// Do things
// Echo things if necessary
break;
}
case 'do_even_more_stuff':
{
// Do things
// Echo things if necessary
break;
}
// Many more cases as needed
}
// Even more common post-processing actions, filters and other stuff any action may need
$output=ob_get_clean();
return $output;
}
You can replace conditional with polymorphism. Create an abstract action class with a method like "execute" and then subclass for all various actions implementing that method.
e.g.
function run_actions(IAction action) {
//...
action->execute();
//...
}
That way, if you will need to introduce additional behavior, you won't need to modify and test long run_actions with numerous responsibilities.
Various disadvantages:
The switch cases all use $vars so they don't have a specific signature.
This hides the signature from the developer that its thus forced to read the code.
you can't do type-hinting on $vars (force parameters to be arrays, instance of some class, etc)
no IDE autocompletion
Easier to do a mistake
forget a break and you're done. No recognizable error.
Difficult to refactor
what would you do if you need to extract the code to a function? You need to duplicate preprocessing (ob_start, etc) or to change everything
what would you do if you needed to run on action with no preprocessing?
I agree it is very simple, but it has long-run disadvantages. Up to you to strike the right balance :)
When I look at this kind of architecture, I see it as beginning to build a new programming language on top of the existing one. This isn't always a bad thing, if the features you're building are a better abstraction than the language you're building them with, but it's worth challenging what those features are.
In this case, the part of the language you're reinventing is function dispatch: you have a named action, which takes arbitrary parameters, and runs arbitrary code. PHP already does this, and quite efficiently; it also has features your system lacks, such as built-in checks of the number and (to some extent) type of parameters. Furthermore, by inventing a non-standard "syntax", existing tools will not work as well - they won't recognise the actions as self-documenting structures, for instance.
The main part you gain is the ability to add pre- and post-processing around the actions. If there were no other way to achieve this, the tradeoff might be worthwhile, but luckily you have better options, e.g. putting each action into a function, and passing it as a callback to the wrapper function; or making each action an object, and using inheritance or composition to attach the pre- and post-processing without repeating it.
By wrapping the arguments in an array, you can also emulate named parameters, which PHP lacks. This can be a good idea if a function takes many parameters, some of them perhaps optional, but it does come with the drawbacks of reinventing processing that the language would normally do for you, such as applying the correct defaults, complaining on missing mandatory items, etc
There is a simple principle that says don't use more than 2 tab indentation.
eg :
public function applyRules($rules){
if($rules){
foreach($rules as $rule){
//apply ryle
}
}
}
Becomes better when you refactor it :
public function applyRules($rules){
if($rules){
$this->executeRules($rules);
}
}
private function executeRules($rules){
foreach($rules as $rule){
$rule->execute();
}
}
And this way your code will be refactored better and you could apply more unit tests than you could.
Another rule says don't use else, instead break the code eg :
public function usernameExists($username){
if($username){
return true;
}else{
return false;
}
}
Instead of doing this, you should do this :
public function usernameExists($username){
if($username){
return true;
}
return false;
}
Only this way you ensure that your code flows 100%
Related
I have classes with larges methods name that are called too many times.
Example:
/**
* This class do awesome things.
*/
class AwesomeClass {
/**
* Do something awesome.
*/
public function doSomethingAwesome()
{
//Do something awesome
}
}
Now, I call the method:
$awesomeInstance->doSomethingAwesome();
The method name is large and is typed to many times. For prevent misspelling, I created a shortcut method:
/**
* Shortcut of doSomethingAwesome()
*/
public function dsa()
{
return $this->doSomethingAwesome();
}
And calling method:
$awesomeInstance->dsa();
Is this a bad practice? How afect the performance?
It is a commonly stated rule of thumb that code is read more often than it is written. Even if writing code only for your own use, you will not remember everything about how it works when you read it back in 6 months time to make some amendment.
Your shortcut method names optimise your codebase for writing, because they make it quicker to type, and less error-prone.
However, they are actively harmful for reading, because they lead to less descriptive code, and require you to cross-reference the comment on the shortcut to check what it abbreviates.
Instead, you should aim to write the same code (thus retaining readability) but find tools to do so more easily and with fewer errors. A major helper in this case would be using a more powerful code editor or IDE, which will include auto-complete and code navigation facilities. You can also use standalone "code linting" / "static analysis" tools to pick up mistyped function names.
Performance shouldn't be an issue. But readability of your code suffers. You should use descriptive method names. If this function is called too many times it's a code smell and some refactoring may be needed.
I would not recommend creating shortcut methods. I am not sure why your methods do have such long names. Maybe your methods do more than one task?
for example:
$awesomeInstance->parseCheckAndSaveMyAwsomeObject();
in that case, you should write a dedicated method for each task.
Could you explain it in more detail?
I have some pattern that works great for me, but that I have some difficulty explaining to fellow programmers. I am looking for some justification or literature reference.
I personally work with PHP, but this would also be applicable to Java, Javascript, C++, and similar languages. Examples will be in PHP or Pseudocode, I hope you can live with this.
The idea is to use a lazy evaluation container for intermediate results, to avoid multiple computation of the same intermediate value.
"Dynamic programming":
http://en.wikipedia.org/wiki/Dynamic_programming
The dynamic programming approach seeks to solve each subproblem only once, thus reducing the number of computations: once the solution to a given subproblem has been computed, it is stored or "memo-ized": the next time the same solution is needed, it is simply looked up
Lazy evaluation container:
class LazyEvaluationContainer {
protected $values = array();
function get($key) {
if (isset($this->values[$key])) {
return $this->values[$key];
}
if (method_exists($this, $key)) {
return $this->values[$key] = $this->$key();
}
throw new Exception("Key $key not supported.");
}
protected function foo() {
// Make sure that bar() runs only once.
return $this->get('bar') + $this->get('bar');
}
protected function bar() {
.. // expensive computation.
}
}
Similar containers are used e.g. as dependency injection containers (DIC).
Details
I usually use some variation of this.
It is possible to have the actual data methods in a different object than the data computation methods?
It is possible to have computation methods with parameters, using a cache with a nested array?
In PHP it is possible to use magic methods (__get() or __call()) for the main retrieval method. In combination with "#property" in the class docblock, this allows type hints for each "virtual" property.
I often use method names like "get_someValue()", where "someValue" is the actual key, to distinguish from regular methods.
It is possible to distribute the data computation to more than one object, to get some kind of separation of concerns?
It is possible to pre-initialize some values?
EDIT: Questions
There is already a nice answer talking about a cute mechanic in Spring #Configuration classes.
To make this more useful and interesting, I extend/clarify the question a bit:
Is storing intermediate values from dynamic programming a legitimate use case for this?
What are the best practices to implement this in PHP? Is some of the stuff in "Details" bad and ugly?
If I understand you correctly, this is quite a standard procedure, although, as you rightly admit, associated with DI (or bootstrapping applications).
A concrete, canonical example would be any Spring #Configuration class with lazy bean definitions; I think it displays exactly the same behavior as you describe, although the actual code that accomplishes it is hidden from view (and generated behind the scenes). Actual Java code could be like this:
#Configuration
public class Whatever {
#Bean #Lazy
public OneThing createOneThing() {
return new OneThing();
}
#Bean #Lazy
public SomeOtherThing createSomeOtherThing() {
return new SomeOtherThing();
}
// here the magic begins:
#Bean #Lazy
public SomeThirdThing getSomeThirdThing() {
return new SomeThirdThing(this.createOneThing(), this.createOneThing(), this.createOneThing(), createSomeOtherThing());
}
}
Each method marked with #Bean #Lazy represents one "resource" that will be created once it is needed (and the method is called) and - no matter how many times it seems that the method is called - the object will only be created once (due to some magic that changes the actual code during loading). So even though it seems that in createOneThing() is called two times in createOneThing(), only one call will occur (and that's only after someone tries to call createSomeThirdThing() or calls getBean(SomeThirdThing.class) on ApplicationContext).
I think you cannot have a universal lazy evaluation container for everything.
Let's first discuss what you really have there. I don't think it's lazy evaluation. Lazy evaluation is defined as delaying an evaluation to the point where the value is really needed, and sharing an already evaluated value with further requests for that value.
The typical example that comes to my mind is a database connection. You'd prepare everything to be able to use that connection when it is needed, but only when there really is a database query needed, the connection is created, and then shared with subsequent queries.
The typical implementation would be to pass the connection string to the constructor, store it internally, and when there is a call to the query method, first the method to return the connection handle is called, which will create and save that handle with the connection string if it does not exist. Later calls to that object will reuse the existing connection.
Such a database object would qualify for lazy evaluating the database connection: It is only created when really needed, and it is then shared for every other query.
When I look at your implementation, it would not qualify for "evaluate only if really needed", it will only store the value that was once created. So it really is only some sort of cache.
It also does not really solve the problem of universally only evaluating the expensive computation once globally. If you have two instances, you will run the expensive function twice. But on the other hand, NOT evaluating it twice will introduce global state - which should be considered a bad thing unless explicitly declared. Usually it would make code very hard to test properly. Personally I'd avoid that.
It is possible to have the actual data methods in a different object than the data computation methods?
If you have a look at how the Zend Framework offers the cache pattern (\Zend\Cache\Pattern\{Callback,Class,Object}Cache), you'd see that the real working class is getting a decorator wrapped around it. All the internal stuff of getting the values stored and read them back is handled internally, from the outside you'd call your methods just like before.
The downside is that you do not have an object of the type of the original class. So if you use type hinting, you cannot pass a decorated caching object instead of the original object. The solution is to implement an interface. The original class implements it with the real functions, and then you create another class that extends the cache decorator and implements the interface as well. This object will pass the type hinting checks, but you are forced to manually implement all interface methods, which do nothing more than pass the call to the internal magic function that would otherwise intercept them.
interface Foo
{
public function foo();
}
class FooExpensive implements Foo
{
public function foo()
{
sleep(100);
return "bar";
}
}
class FooCached extends \Zend\Cache\Pattern\ObjectPattern implements Foo
{
public function foo()
{
//internally uses instance of FooExpensive to calculate once
$args = func_get_args();
return $this->call(__FUNCTION__, $args);
}
}
I have found it impossible in PHP to implement a cache without at least these two classes and one interface (but on the other hand, implementing against an interface is a good thing, it shouldn't bother you). You cannot simply use the native cache object directly.
It is possible to have computation methods with parameters, using a cache with a nested array?
Parameters are working in the above implementation, and they are used in the internal generation of a cache key. You should probably have a look at the \Zend\Cache\Pattern\CallbackCache::generateCallbackKey method.
In PHP it is possible to use magic methods (__get() or __call()) for the main retrieval method. In combination with "#property" in the class docblock, this allows type hints for each "virtual" property.
Magic methods are evil. A documentation block should be considered outdated, as it is no real working code. While I found it acceptable to use magic getter and setter in a really easy-to-understand value object code, which would allow to store any value in any property just like stdClass, I do recommend to be very careful with __call.
I often use method names like "get_someValue()", where "someValue" is the actual key, to distinguish from regular methods.
I would consider this a violation of PSR-1: "4.3. Methods: Method names MUST be declared in camelCase()." And is there a reason to mark these methods as something special? Are they special at all? The do return the value, don't they?
It is possible to distribute the data computation to more than one object, to get some kind of separation of concerns?
If you cache a complex construction of objects, this is completely possible.
It is possible to pre-initialize some values?
This should not be the concern of a cache, but of the implementation itself. What is the point in NOT executing an expensive computation, but to return a preset value? If that is a real use case (like instantly return NULL if a parameter is outside of the defined range), it must be part of the implementation itself. You should not rely on an additional layer around the object to return a value in such cases.
Is storing intermediate values from dynamic programming a legitimate use case for this?
Do you have a dynamic programming problem? There is this sentence on the Wikipedia page you linked:
There are two key attributes that a problem must have in order for dynamic programming to be applicable: optimal substructure and overlapping subproblems. If a problem can be solved by combining optimal solutions to non-overlapping subproblems, the strategy is called "divide and conquer" instead.
I think that there are already existing patterns that seem to solve the lazy evaluation part of your example: Singleton, ServiceLocator, Factory. (I'm not promoting singletons here!)
There also is the concept of "promises": Objects are returned that promise to return the real value later if asked, but as long as the value isn't needed right now, would act as the values replacement that could be passed along instead. You might want to read this blog posting: http://blog.ircmaxell.com/2013/01/promise-for-clean-code.html
What are the best practices to implement this in PHP? Is some of the stuff in "Details" bad and ugly?
You used an example that probably comes close to the Fibonacci example. The aspect I don't like about that example is that you use a single instance to collect all values. In a way, you are aggregating global state here - which probably is what this whole concept is about. But global state is evil, and I don't like that extra layer. And you haven't really solved the problem of parameters enough.
I wonder why there are really two calls to bar() inside foo()? The more obvious method would be to duplicate the result directly in foo(), and then "add" it.
All in all, I'm not too impressed until now. I cannot anticipate a real use case for such a general purpose solution on this simple level. I do like IDE auto suggest support, and I do not like duck-typing (passing an object that only simulates being compatible, but without being able to ensure the instance).
In a class I have a handling method which performs actions based on what post/get variables are available. Here is a simplified example:
public function handleAll(array $vars) {
if (isset($vars['var1'])) {
$this->doThisAction($vars['var1']);
} else if (isset($vars['var2'])) {
$this->doAnotherAction($vars['var2']);
}
}
So at runtime the method could be called like this $handler->handleAll($_POST).
The problem is that the variables stored in the $vars array must be named "var1" & "var2" etc. This means that the handling code is coupled with the names of html form elements or get variables in urls (or indeed the keys of any array passed in).
Allowing an array of any variables to be injected makes the method flexible which is necessary as it is also polymorphic. Classes inherit this method and use it to call it's own actions. A result of this is that it is not obvious from the outside what the handling method requires for it to function. This means that the implementation must be examined to find out what it does (unless I copy the code into the phpdoc which would be silly).
I'm not sure how to get around this. Having enormous if/case statements all exposed on index pages (from multiple classes like these) makes for extremely messy code so it is preferable to have all this encapsulated within a method. Also, it makes sense to have it as a method of the class responsible for calling the actions that manipulate it's own state (responsibility driven design). I thought about having each variable as a parameter for the method but for some classes the parameter list would be quite large. Also it would mean that many handleAll() methods of different classes cannot be called automatically as all parameters would need to be explicitly injected for each call, removing the polymorphic aspect.
To sum up I need to keep the method polymorphic but I need some way of decoupling the handling code from user input from html forms or urls. In doing this perhaps there is a way of separating the interface from the implementation too. I'm surprised I can't find any solutions to this already, it seems like it would be a common problem.
I'm not sure if what you describe can even be avoided.
If you have code which requires specific arguments to it - be it in the form of an array containing certain values or whatever else - that function will always depend on the arguments being populated correctly.
If I have function foo($a, $b, $c), I will always have to fill the parameters. If they come from POST, then I would need to take them from POST. The same goes even if the function took an array('a' => ..., 'b' => ..., 'c' => ...) instead.
However, if you wanted to decouple your forms from the parameter list, you could use a simple function to transform the POST array into the format expected by one of your handler functions. I do not really see any reason for this though, since why add the extra complexity if you don't have to?
If you wish to better define what kind of array your handler requires, you could consider having it require an object instead. This approach is sometimes called a parameter object.
The benefits of using a parameter object is that you can easily set up required parameters in the constructor, and use setters for optional ones. Anyone who uses your code can just look at your object's interface to see what values are required.
If the values your handlers use are actually related to each other, I would consider creating an actual business logic type object. Create it using the data from POST, and move the code which processes the values into a method in the object.
Decoupling and automating handling methods, can be painful methods to narrow down too. But there is not so many options, other than, create handlers for eveything. However, I would you like add something. Allow defaults and support variation
function handlePOST() { }
function handleGET() { }
function handleArr() { }
Now, these function can hold the variations of the function on their behalf.
Inside however, they is not better way other than to code all the handlers. But switch is shorten them and use separate function for the handling.
switch($_POST['var1']) {
case "value1": callPostValue1(); break;
default: callPostDefaultHandler(); break;
}
I'm working on a PHP page. The core page has most of the code under one if statement, and the rest of the code under an "else" statement. I'm about to add a third option, "edit" which will make the page yet longer. What is a good strategy to break up the logic into more readable chunks? Subroutines? I've gotten used to OO through Java. Web scripting seems to make it too easy to get into overly long if / else blocks.
classes, functions, includes.
I like to put content modules that occur on more than one page into their own file and just use include_once() statements. For instance you can keep one php file for your page header and just reuse it on multiple pages.
also, make sure to learn about the __autoload function. it automatically loads classes only when needed.
If you can, check out the MVC design pattern.
You should be able to add a controller which you can group your actions under.
You could incorporate routing so a URL like this...
/user/edit
Would call this in PHP (simplified)...
$controller = new User;
$controller->edit();
Of course, you'd need to use variable function names to get that to work dynamically :)
long if-else is inevitable if you comparing multiple set of conditions
you can make use of switch statement
Or in PHP 5.3, goto has been introduced (beware of the dinosaur)
Or wrap the conditions inside a function/method
function lots_of_conditions($id, $type, $category)
{
if ($id==1) return 1;
if ($type=='member') return 2;
if ($category=='keyword') return 4;
return 8;
}
$status = lost_of_conditions($id=2, $type='x', $category='y');
switch ($status)
{
case 1: ...; break;
case 2: ...; break;
case 4: ...; break;
default: ...; break;
}
You've got some real design issues to consider. And MVC as suggested by alex is probably a good idea. You should consider what is common between these three cases, and you might find that they're differences can be accounted for with a few variables.
For the meantime, to avoid your spaghetti code, you can put each of these three cases into a function. I don't know if these functions just need to echo something, or need return values, or what, but you should be able to figure that out.
Among other things, you can either have these as functions in the same file (if this 'core' file has a class, consider making these member functions) or put them in a static class elsewhere. I'll show the static option.
class BodyFunctions
{
public static function one() {echo "this is what you now call the 'if' block";} //think of better names for these first two
public static function two() {echo "this is what you now call the 'else' block";}
public static function three() {echo "this is the edit part you want to add";}
}
And to call them from your 'core' page
BodyFunctions::$mode(); //where mode is 'one', 'two', or 'edit'. like the variable your if statements are currently based on
Or you can just add the functions to your core page like this, and call them with call_user_func
function one() {echo "this is what you now call the 'if' block";}
....
call_user_func($mode);
What I wouldn't recommend is putting these functions in a class you need to instantiate even though this class lacks any kind of state and the sole purpose of instantiation is to run the functions inside. Doing tihs unnecessarily is kind of a pet peeve of mine.
I can't stress enough that this should be considered an intermediate measure to avoid the if-else chain or switch situation you've got going on. Look into MVC or a more thorough solution.
Consider that PHP is usually hosted behind some web server that is already using a "Front Controller" pattern - Apache or Lighttpd or similar. If your file logic looks like this:
/dingbat.php
// local page setup, function definitions, etc...
function foo() { ... }
function bar() { ... }
function everything_else_useful_on_this_page() { ... }
print_standard_header($TITLE);
if ($mode == "edit") {
do_edit_lots_of_hairy_logic();
} else if ($mode = "new thing") {
who_knows();
}
else {
print_form();
}
print_standard_footer();
...you can replace it with a set of files that look like this:
/dingbat/common.php
function foo() { ... }
function bar() { ... }
function everything_else_useful_on_this_page() { ... }
/dingbat/edit.php
include "common.php"
print_standard_header($TITLE);
do_edit_lots_of_hairy_logic();
print_standard_footer();
/dingbat/new-thing.php
include "common.php"
print_standard_header($TITLE);
who_knows();
print_standard_footer();
/dingbat/index.php
include "common.php"
print_standard_header($TITLE);
print_form();
print_standard_footer();
There are a ton of benefits to doing this:
It's less code to write than implementing your own Front Controller.
It's less code for the PHP runtime to execute than a single page, and therefore faster.
It scales out to an arbitrary number of possible modes (limited only by your filesystem).
It is recursive (imagine /dingbat/edit/easy.php vs /dingbat/edit/expert.php, with a private /dingbat/edit/common.php for shared functionality).
It keeps individual files smaller and therefore easier to "master" by developers.
It automatically makes URLs more discoverable to both users and search engines (instead of /dingbat.php?mode=edit&a=1&b=2 you have /dingbat/edit.php?a=1&b=2 - the "variables" are obvious).
If your page is public, it improves SEO and proxy behavior (URL path is far more important than the querystring). ModRewrite can work around SEO issues, but with this scheme it's simply unnecessary.
The downsides?
You have to change existing URLs (consider replacing the existing /dingbat.php with a page that redirects).
The common.php files may seem unnatural at first, because it is mostly "page-local logic". This will go away over time.
If the common logic overwhelms the individual page content, then there isn't very much benefit.
Other users have mentioned the MVC pattern, which works in concert with this file-based architecture.
This isn't really an answer, because the question is PHP, but I'm considering converting the whole code base to Rails. I know PHP has MVC frameworks like Cake, but it seems as if RoR has MVC built into it - enforced. Now, how long is it gonna take me to do this when I just wanted to add an edit function?
(Note: this is related to this question, but I think it could have been written more clearly, so I'm trying again -- my update only helped to a limited extent.)
I've inherited some code that creates a complex form with numerous sections, and lots of possible views, depending on a number of parameters. I've been working with it for a while, and finally have a chance to think about doing some re-factoring. It's currently written procedurally, with a bunch of functions that look like this:
get_section_A ($type='foo', $mode='bar', $read_only=false, $values=array()) {
if ($this->type == 'foo') {
if ($this->mode == 'bar') { }
else { }
} else { }
}
Passing around those parameters is nasty, so I've started writing a class like this:
class MyForm {
public $type; // or maybe they'd be private or
public $mode; // I'd use getters and setters
public $read_only; // let's not get distracted by that :)
public $values;
// etc.
function __constructor ($type='foo', $mode='bar', $read_only=false, $values_array=array()) {
$this->type = $type;
// etc.
}
function get_sections () {
$result = $this->get_section_A();
$result .= $this->get_section_B();
$result .= $this->get_section_C();
}
function get_section_A() {
if ($this->type == 'foo') { }
else { }
}
function get_section_B() {}
function get_section_C() {}
// etc.
}
The problem is that the procedural functions are split into a few files (for groups of sections), and if I combine them all into a single class file, I'm looking at 2500 lines, which feels unwieldy. I've thought of a few solutions:
keep living with the nasty parameters and do something else with my time :)
live with having a 2500 line file
create a separate class for each group of sections that somehow "knows" the values of those parameters
If I do #3, I've thought of two basic approaches:
pass the MyForm object in as a single parameter
create a FormSectionGroup class with static properties that get set in MyForm, then in the group files, each class would extend FormSectionGroup and automatically have access to the current values for those parameters.
1) is probably easier to set-up, and once I'm inside get_section_A() whether I say $this->type or $myForm->type isn't all that different, but it's not exactly OOP. (In fact, I could do that without really changing to an OOP approach.)
Are there other approaches? Thoughts about which is better?
I would love nothing more than to write a lengthy explanation of how to do this, but I'm feeling a bit lazy. I do however have enough energy to point you instead to Zend_Form from the zend framework. There may be some dependencies to make it work properly (Zend_View, Elements, Decorators), but once you have them, it handles that type of situations quite gracefully.
I thought of this when I posted in your previous question - this problem reeks of decorator pattern.
It's going to be no small task, though. But I think you'll have an amazing sense of satisfaction/accomplishment once you get it done.
Having done a lot of Cocoa programming recently, I tend to view things in MVC-pattern (Model-View-Controller) terms. Hence, I'd look at the form as a controller of its various sections.
Each section object should be responsible for keeping track of its status, values and whether or not it should be displayed. Or to be precise, the section_model would take care of the values (default values, validation, etc), the section_view would take care of displaying (or not) parts of the section and the section_controller would send keep track of the status of the section and report results to the form object.
The form object should instantiate the section controllers, tell them to display or hide or whatever, and get status reports. The form object, really act a controller, can then decide when the form if completely filled out. You may have a form_model object to save the collected data, or maybe you'd rather have the section_model objects take part of that. It will take a while to get a feeling for how the different objects interact but I know from experience that if you are disciplined in designing your objects (the key is: what is an object's responsibility and what is not), you will gain a better overview and your code will be easier to upgrade. Once you find that improvements start to arise naturally, you are on the right track.
If you have the time to work on doing #3, you will probably be the happiest over the long run. Personally, I don't have the time in my job to refactor to that degree very often, and so I'd probably be forced to pick #1. #2 sounds like the worst of both worlds to me. Lots of work to get code you don't like.