views:

101

answers:

4

I know the benefits of chaining within PHP but lets say we have this following situation

$Mail = new MailClass("mail")
        ->SetFrom("X")
        ->SetTo("X")
        ->SetSubject("X")
        ->AddRecipient("X")
        ->AddRecipient("X")
        ->AddRecipient("X")
        ->AddRecipient("X")
        ->AddRecipient("X")
        ->AddRecipient("X")
        ->Send();

Are there any issues with returning and reusing the object over and over again, issues such as speed or failure to follow best Practises

Also a good read on this if your new to Fluent-Interface's: Martin Fowler on Fluent-Interfaces

I Fully understand that it doesn't have to be programmed this way, and can be handled like so:

$Mail = new MailClass("mail");
$Mail->AddRecipien(
    array(/*.....*/)
);
$Mail->SetFrom("X");
$Mail->SetTo("X");
$Mail->SetSubject("X");
$Mail->Send();

but lets say I have an object like so:

$Order = new Order()
         ->With(22,'TAL')
         ->With(38,'HPK')->Skippable()
         ->With(2,'LGV')
         ->Priority();

Note the ->With(38,'HPK')->Skippable(), This is perfect example of a Pro for this type of programming

+4  A: 

If you have to validate Something, i think it makes more sense to validate it in the AddRecipient Method itself, but the Performance should be about the same. And I'm not aware of any general disadvantages of using method chaining.

Hannes
A: 

EDIT: Updated answer to match the question
Function calls are slower than loops, i.e. chaining for example the addRecipient() method degrades performance a tiny bit compared to calling an addRecipients() method that takes an array that is then processed in a loop.

Furthermore, more sophisticated method chaining up to fluent APIs may require additional book-keeping of data related to the last called method so that the next call can continue working on that data because all methods return the same object on which chain is built. Let's take a look at your example:

...
->With(22, 'TAL')
->With(38, 'HPK')->Skippable()
->With(2, 'LGV')
...

This requires you to remember that Skippable() is to be applied to (38, 'HPK') rather than (22, 'TAL').

You will hardly notice a performance loss though unless your code is going to be called very frequently in a loop or when you have so many concurrent requests to your web server such that it approaches its limits (which is the case for heavy-load websites).

Another aspect is that the method chaining pattern enforces the use of exceptions to signal errors (which I'm not saying is a bad thing, it just differs from the classical "call and check function result" style of coding).

There will usually be functions however which yield other values than the object to which they belong (e.g. those returning the status of the object and accessors). It is important that the user of your API can determine which functions are chainable and which are not without having to refer to the documentation each time he encounters a new method (e.g. a guideline that says that all mutators and only mutators support chaining).


Answer to the original question:

[...] The issues i have with chaining is the fact that you cant really perform extra validation [...]

Alternatively, implement a dedicated validation method that you call after setting all properties and have it return you an array of validation failures (which can be plain strings or objects, e.g. named ValidationFailure).

Archimedix
A: 

This is a double edged sword.

The good side? This is cleaner than re-addressing the class and although it is mostly just a syntax change it is going to speed up the processing a little. It would be preferable to loop this kind of a chain than to loop each call in longform.

The bad side? This will cause security issues when people first get used to it. Be diligent in your purification of incoming vars on this so you don't pass something in there you shouldn't. Don't over-complicate your classes.

  1. Pre-validate your information.
  2. Pre-authorize your users.

I see no problem with chaining these methods into a loop, performancewise.

Geekster
+1  A: 

You can't chain directly from the class instantiation:

$Mail = new MailClass("mail") 
            ->SetFrom("X")
            ->SetTo("Y");

you have to instantiate first, then chain against the instantiated object:

$Mail = new MailClass("mail") ;
$Mail->SetFrom("X")
     ->SetTo("Y");

If you validate within the individual setter methods (as you should) then you need to ensure that you throw exceptions when a validation error is encountered. You can't simply return a boolean false on error, otherwise the chaining will try to call the next method against a boolean rather than you class instance and you'll get.

Fatal error: Call to a member function SetSubject() on a non-object in C:\xampp\htdocs\oChainTest.php on line 23

if you throw exceptions, you can wrap the chain within try... catch

$Mail = new MailClass("mail");
try {
    $Mail->SetFrom("X")
        ->SetTo("Y")
        ->SetSubject("Z");
} catch (Exception $e) {
    echo $e->getMessage();
}

but as a warning, this will leave the instance in a partially updated state, there's no rollback (unless you write one yourself) for the methods that did validate/execute successfully, and the methods following the exception won't be called.

Mark Baker
A workaround to this is the use of factory methods to instantiate objects and chain from there, e.g. `MailClass::create('mail')->setFrom('X')`. The downside in PHP versions before 5.3 is that they have no late static binding, i.e. static methods would refer to the class where they were defined. Additionally, static method calls are slow.
Archimedix
@Archimedix - valid point, and late static binding will be a godsend (once I can enforce that my libraries are always run against 5.3+)
Mark Baker
I always use an abstract registry with static methods to instantiate objects and store them within an array so my out come would be `Registry::Use("Mail")->X()->Y->Z();` where initialization is done at within the method: `Registry::Use("Mail")`
RobertPitt