6

I'm evaluating my current PHP practices in an effort to write more testable code. Generally speaking, I'm fishing for opinions on what types of actions belong in the constructor. Should I limit things to dependency injection? If I do have some data to populate, should that happen via a factory rather than as constructor arguments? (Here, I'm thinking about my User class that takes a user ID and populates user data from the database during construction, which obviously needs to change in some way.)

I've heard it said that "initialization" methods are bad, but I'm sure that depends on what exactly is being done during initialization.

At the risk of getting too specific, I'll also piggyback a more detailed example onto my question.

For a previous project, I built a FormField class (which handled field value setting, validation, and output as HTML) and a Model class to contain these fields and do a bit of magic to ease working with fields. FormField had some prebuilt subclasses, e.g. FormText (<input type="text">) and FormSelect (<select>). Model would be subclassed so that a specific implementation (say, a Widget) had its own fields, such as a name and date of manufacture:

class Widget extends Model {
    public function __construct( $data = null ) {
        $this->name = new FormField('length=20&label=Name:');
        $this->manufactured = new FormDate;

        parent::__construct( $data ); // set above fields using incoming array
    }
}

Now, this does violate some rules that I have read, such as "avoid new in the constructor," but to my eyes this does not seem untestable. These are properties of the object, not some black box data generator reading from an external source. Unit tests would progressively build up to any test of Widget-specific functionality, so I could be confident that the underlying FormFields were working correctly during the Widget test.

In theory I could provide the Model with a FieldFactory() which could supply custom field objects, but I don't believe I would gain anything from this approach. Is this a poor assumption?

1 Answer 1

1

Several of the 'rules' you're hearing about using Factories are to assist in making the code more easily modified in the future, not about making it testable now. Take a look at the parameters that you are sending to the FormField constructor: 'length=20&label=Name:'.

That argument is now effectively 'hard-coded', & 'client-side'.

This means that if you have 10 other widgets, each of those 10 would duplicate that 'new'. If you later decide that you want to change the label from 'Name' to something else, or to increase the length to 25, you have to go back and change the code in 10 places, miss 1 and you have a bug which could show up as a weird layout issue.

Using a factory to call that new, allows those 10 calls to all call one factory method, and in the case I just described only the factory method would need to change.

3
  • Doesn't seem to fit that example exactly (Widget may have a 25 character name, other names may be a different length) but I can see how factories would apply to other objects. Maybe the User class would accept other objects as part of DI, with a User::get_by_id() factory that would automate injecting the database interface, perform $user->load() with the passed id, and return the generated object. Jan 1, 2011 at 21:24
  • I was going to make the answer more complicated with a domain example, and decided against it, but seeing as you comment went down that road, here ya go: MVC separation has been best practice for quite a while, and this is an area where they mix (which is bad). When defining Widget, it's better to use a domain specific class (model), and let it call into a presentation specific class in the factory (view). To do that, you abstract the notion of the different 'kind' of boxes without trying them directly to implementations, so you might have the User Widget ask for a User Textbox. Jan 2, 2011 at 10:31
  • The Factory call then would be to a specific type of box (either UserTextboxFactory, or TextBoxFactory(UserTextBox). This allows the box construction int the presentation layer to be just as OOP and sharing as the User widget in the model, but still separate out how it looks from how it 'works' (like field validation, callback event, etc.) Jan 2, 2011 at 10:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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