Jump to content

How far do I go to maintain SRP (do I separate output from template)?


Recommended Posts

I have some code that takes "User Input", then does a series of computations and produces "Output Data" to be displayed on a PDF document.  PDF document is a template that is subsequently displayed with data superimposed onto it in the appropriate places.

One complication is that PDF document is a template, and template file name itself depends in part on data from the "User Input" and part from the computed "Output Data".

In original (legacy) code, PDF template file name is embedded into "Output Data".  So I could do

$outputData = $this->computeOutput($userInput);

$pdf = new Pdf();
//$outputData['template'] == 'computed_pdf_filename.pdf';
$pdf->setTemplate($outputData['template']);
$pdf->superimposeData($outputData);

My question is ... do I separate computation of the PDF template (which does not have much to do with outline data) from computation of the outline data?

They both share a good chunk of transient variables to compute things. so in some ways it makes sense to keep "PDF template" as part of "Output Data".

But on the other hand, what if I later decide to not use PDF at all but to use PNG Images?  Or TIFF?  Or some other format?  Unlikely, but then I will need to rip out my PDF-Template computing code from "ComputeOutput" method.

 

But the to compute Template Filename I will need to pass that function both the input and the output.  Something like so

$outputData = $this->computeOutput($userInput);

//if I separate it out
$template = TemplateService::computeOutput($userInput, $outputData);

$pdf = new Pdf();
//$outputData['template'] == 'computed_pdf_filename.pdf';
$pdf->setTemplate($template);
$pdf->superimposeData($outputData);

Back to the question ... are there any pros or cons to separating data from the template?

 

So the question is who computes the output data, who determines the template, and how do they share the necessary information with each other?

 

In MVC terms you have the controller, view, and model. Looks like the code you posted comes from the controller? Then the view should be the appropriate template, according to the output, while the model is the part that computes the output data from the input. The model should not be aware of the view - that is, determine the template to use for displaying its data. And inside the view is too late to determine which view to use.

 

Then it's up to the controller to decide. If determining the template is easy, like a few lines of code or something not too big and scary to put into a helper method, then I'd put it in the controller: this logic won't be used elsewhere (I guess?) so it should stay as "close" to the controller as possible.

public function controller() {
	$model = new ClassThatComputesData();
	$output = $model->compute($input);

	if ($output->property == "special value") {
		$this->view = "special_view.php";
	} else if ($output->method() > MAGIC_VALUE) {
		$this->view = "magic_value_view.php";
	} else {
		$this->view = "generic_view.php";
	}
}
May seem weird but the whole point of a controller is to shuffle information between the various models and various views, so determining which view to serve some model is part of its job.

 

Otherwise I'd go for a view factory. It's code somewhere specifically dedicated to figuring out which view is appropriate for given input data. The controller can simply use the view factory to get the appropriate view.

public function controller() {
	$model = new ClassThatComputesData();
	$output = $model->compute($input);

	$this->view = ClassThatCreatesViewsFromOutputData::factory($output);
}
public function factory(OutputModel $model) {
	// lots of work that will eventually {
		return "particular_view.php";
	// }
}

So the question is who computes the output data, who determines the template, and how do they share the necessary information with each other?

Yes ... I am not entirely sure if the first approach will work since there are more than a few lines of code to figure the View Template out, and those lines of code include some "business logic" as well. 

 

For example part of $input is a boolean $isMotorPresent, and other engineering type values.  Part of generated $output is "How many supporting stands does this product have" which take some hefty computation to figure out.  "presence of motor" is not part of $output, although I suppose it could be.... That's kind of a judgement call.  Both pieces (from $input & $output) are needed to pick the right View template.

 

So a problem remains..  My particular View and the way my code is currently set up, my "View Factory" or my "Code that's close to the controller", needs to know $input as well.

So, for example I would have to do this:

$this->view = ClassThatCreatesViewsFromOutputData::factory($input, $output);

Or I would have to copy certain values from $input to $output so I can do this:

//whether $output is bloated I guess is relative, I can say it's not.. 
//But the bloated part (one copied over as pass-through from $input) will only used to compute the View template
//and will not be used otherwise.
$this->view = ClassThatCreatesViewsFromOutputData::factory($bloated_Output_That_Contains_A_Bit_Of_Input);

And that was my problem .... i.e. I can either

  • 1. put up with using both $input and $output in my code in my View Factory (or make the $output contain $input)
  • 2. compute part of View (the template part) inside my ClassThatComputesData, probably breaking SRP, because ClassThatComputesData already contains $input and the view-template-required $output is being created as part of ClassThatComputesData
  • 3. make View-template accept $input only and recompute the needed $output vars

All of the above will work but I think is somehow breaking SRP or at least creating some bloat/rework........  #2 is how my legacy code was, and my current code is a little weird because I essentially do array merge .... which I see as a hack

$this->view = ClassThatCreatesViewsFromOutputData::factory(array_merge($input, $output));

Yeah, I don't like the idea of combining the input and output just to pass it all around either. Passing both separately wouldn't be that bad...

 

Do you need the entire input object, either because you need to know a lot of it or because you don't know which parts are relevant? If you only needed a couple pieces of data, and you knew which pieces, then I'd deal with those separately. Like

$this->view = ClassThatCreatesViewsFromOutputData::factory($input["field1"], $input["field2"], $output);
The idea is to reduce the dependency on the structure of the input data - at least as far as the view generation goes. One way to think about that is "what would I do if the input method changed (eg, command-line program instead of an HTML form)" or "how could I make this easier to test automatically using unit testing" *. Decoupling the relevant input data (field1 and field2) from the actual input itself helps this part easier.

 

Question: given that the hypothetical field1 and field2 are part of the input data, implying that they are potentially necessary when generating the output data, would you be able to get the necessary information from the output instead? Everything you've said so far is pretty abstract so I have no idea whether that's possible, or whether the question even makes sense, but that's the next place I would look. You've probably already considered and discarded this idea.

 

Otherwise... I think you're stuck with passing the input and output data around with each other.

$this->view = ClassThatCreatesViewsFromOutputData::factory($input, $output);
It then means that the view is necessarily coupled to the structure of both the input and output data, but I can't think of any better options.

 

 

* Unit testing. Something everybody should do but nobody ever actually does.

There are two potential tests here:

1. Given particular input data, derive the correct output data

2. Given particular output data and required other data (eg, the input's field1 and field2), derive the correct view

(and one could consider a third too: deriving the correct view from particular input data, which is equivalent to tests 1+2)

Having the process broken down into the two steps makes it easier to locate bugs when something goes wrong, and means needing to write less code to test multiple varieties of input and output.

 

Testing #1 is not a problem. Testing #2 removes the idea of "input data" from the equation entirely, which is why I would try to keep the entire $input away from the process of determining the view. But as I said, there may not be any better way to deal with it, and you may be forced to say that the "required other data" is potentially everything from the input.

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.