1

Here's an situation that has haunted one of my open-source projects for quite some time.

Imagine if you could represent every front-end input form with a single backend object? An object that contains properties and anonymous functions (for bits of logic...such as perhaps a call to a database repository to query and bring in all the values for your drop-down, etc). The class would be very basic, but would be decorated by a decorator that would add all the features you need. The decorator will take each decorated client (the class that represents your form) and provide features that make things like "input cascading", "input ID prefixing to avoid name collisions", "XSS protection", etc. a breeze! Giving your HTML input some backend server-side functionality will now be easy!

However, what concerns me is that in some cases the decorated class needs to know some status from its decorator (ack! Code Smell? Coupling?). What I did to get around that is to provide extra properties in a Factory class for providing status. See simple (but long enough to be "real world") example below:

Decorator

Class InputDecorator{
    private $client;

    // Inject the client object to decorate
    public function __construct(InputInterface $client){
         $this->client = $client; 
         $this->client->input = $this->client->setupInput(); 
    }

   // Perform an Input cascade
   public function cascade($inputName){
         foreach($this->client->input[$inputName]['children'] as $child){
              $this->client->input[$child]->cascadeStatus = true;   // <-- Set status here
              $this->outputAJAX($child);
         }              
   }
   // Return HTML content
   public function outputHTML(){
        foreach($this->client->input as $input){
               $dropDownValues = $input['drop-down-values'];
               // take the dropdown set of values and render <select><option...
        }
    }        
    // Return Script(JQuery) content
    public function outputAJAX($inputName){
        $dropDownValues = $this->client->input[$inputName]['drop-down-values'];
        // take the dropdown values and render $(input-name-here).html('<option...
    }

    // overloading magic methods here (typical of decorator pattern)
}

Decorated class (there will be lots of these, each representing its own input form)

Class InputExample implements InputInterface{

    public $input = array();

    public function setupInput(){
        $_this = $this; (for php 5.3 reasons)

        $this->input['states']                     = InputFactory::make("select");
        $this->input['states']->class              = "some CSS class";
        $this->input['states']->style              = "Inline styles are bad!";
        $this->input['states']->children           = array("cities");
        $this->input['states']->values             = function() use ($_this){

              // ORM->query for list of states
              // return array of states 
        }

        $this->input['cities']                     = InputFactory::make("select");
        $this->input['cities']->class              = "";
        $this->input['cities']->style              = "";
        $this->input['cities']->values             = function() use ($_this){

              if($_this->input['cities']->cascadeStatus == true)  // <-- status check
                   // return array of cities in the selected state
              else
                   // return array()
        }
    }

}

Factory class

class InputFactory implements InputFactoryInterface{

    public static function make($type)
    {
        if ($type == "select") {
            return new InputTypes\Select($id);  //<-- cascadeStatus property is defined in here
        } elseif ($type == "text") {
            return new InputTypes\Text($id);
        } elseif ($type == "checkbox") {
            return new InputTypes\CheckBox($id, "hidden");
        }
        // Room to grow...
    }
}

Investigate my use of cascadeStatus, which is the status the Decorator sets to true. The decorated client class needs to know about this status to perform a decision in its anonymous function. This whole solution acts as sort of a Decorator + Mediator/Factory pattern. Is this the correct approach and/or use of these patterns?

  • This is not what one would normally call "decoration". In general, a decorated class has no idea it has been decorated. What you've got here are just a few classes that are very tightly coupled (moreso than is proper, in my opinion). – Ross Patterson Feb 6 '15 at 3:28
  • RIGHT! But I had no other words to start to describe what I'm trying to do. How to reduce coupling? What design pattern to use? I mean, the client class is really just like an XML file, but with some anonymous functions added. It's just being used to create the backend needs for forms. However, those anonymous functions in rare occasions may need to check something in the 'decorator' (rare, but sometimes needs to know about the decorator...so for the most part the decorator is just decorating). – David Graham Feb 6 '15 at 3:32
  • If you can form an answer Ross, I'm here to read and accept an answer! – David Graham Feb 6 '15 at 3:34
  • I thought perhaps there was some need for "mediator" pattern going on here as well. – David Graham Feb 6 '15 at 3:42
  • @RossPatterson what do you think of my answer? Would this be an acceptable solution in your experience? – David Graham Feb 6 '15 at 4:24
-1

I believe what you are trying to do is to reinvent Symfony Forms and add some bunch of ORM to them ;)

They indeed contain all the data from the form and all the form HTML structure described in a very declarative way with a possibility of extension.

But I think you are wrong when you want forms to be able to query backend data. This is incorrect.

Form should not know how to actually query data. You violate SRP here. Form is only for handling input from the user and, may be, validating it (but actual validation should be performed by means of a separate validator class)

What you should do is to query data using Services or Repositories (depending on your architecture complexity) in Controllers and then create Forms using that data. When user submits form, you validate user data there and then again call Service (or Repository) to store that data into the backend.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.