7

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

RobertPitt
  • 56,863
  • 21
  • 114
  • 161
  • Classes should, IMO, validate their arguments for instantiation and test/control arguments of their methods. In my mailer class, is_valid_email is a private method inside the class. – AutoSponge Sep 29 '10 at 13:13
  • the "->With(38,'HPK')->Skippable()" Situation isn't nescecarly a Problem, for example in this Scenario you/the class can know that Skippable applies to the last With that has been added. – Hannes Sep 29 '10 at 13:39
  • 2
    Yes but the readability factor is greater then the normal methods of compiling this block of code. That is just a pro for readability – RobertPitt Sep 29 '10 at 13:46
  • Personally, I don't care for fluent interfaces in most code. They are useful in libraries, but as far as production code I think they mask too much of what's going on to be readable. To understand a fluent interface, you must understand the interface of each method called. Does `->With()` return the `Order` object? Or does it return another object? If you use it explicitly you know right away (since you can see what it's assigned to)... So I think it actually hurts readability... – ircmaxell Sep 29 '10 at 14:05
  • 1
    As a side-note, in PHP method names usually start with a lower-case letter. – Arc Sep 29 '10 at 14:06
  • That is a good point ircmaxwell, lets say I developed a MVC Framework for new programmers, and made all methods chainable, and encouraged this, it would seem more of a SQL, `('Structured Query Language')` or particularity in this case `fluent interface` – RobertPitt Sep 29 '10 at 14:12
  • @Robert: Hm, looks like you basically changed the entire question... :/ – Arc Sep 29 '10 at 14:50
  • I submit for your perusal, [the definitive method chaining decision tree](http://chat.stackoverflow.com/transcript/message/4316194#4316194) –  Aug 15 '12 at 06:30

4 Answers4

5

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
  • 8,147
  • 4
  • 33
  • 51
2

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
  • 209,507
  • 32
  • 346
  • 385
  • 2
    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. – Arc Sep 29 '10 at 13:55
  • @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 Sep 29 '10 at 14:00
  • 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 Sep 29 '10 at 14:09
  • You CAN chain directly from the class instantiation in PHP 5.4+ see: [http://stackoverflow.com/a/2188690/3200414] – Blablaenzo Jan 09 '17 at 00:51
1

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).

Arc
  • 11,143
  • 4
  • 52
  • 75
0

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
  • 336
  • 1
  • 10