如何判断链中的最后一个方法?

I've started using method chains in my PHP app:

$object->do()->something();

Specifically with querying databases:

$database->select()->from('mytable');

However at the moment I need to call a function to actually perform the query at the end:

$database->select()->from('mytable')->perform();

This is annoying because usually every chain will perform straight away. Sometimes it wouldn't, but 9 times out of 10 it will. I need this to work with any object, not just a database handler.

Is there a way to tell if the last method I call is the last one in the method itself, so it would perform() without me telling it to?

If not, I might just try to use some keywords like ->now() instead of ->perform()

I have a few idea(s):

  • each method has a parameter that if set will let it know it's at the end (maybe it can loop through all parameters to find if a special constant was passed?) eg $object->do()->something(now);

Using an operator overloading extension, you can specify one of the unary operators for prompting executions

Example:

~$db->select()->from()->where();

If you want to avoid installing an extension, one suggestion I can make is using some kind of escape character in your latest chain call to promp the execution. You should omit it from the final query to avoid syntax errors.

Example:

$db->select()->from()->where("id = 1 ~"); //~ character implies automatic execution

Is there a way to tell if the last method I call is the last one in the method itself

No, I believe there is no such thing. That's because "chain calls" do not differ from "one by one calls". That's just a kind of syntactic sugar for "call => return object => call => return object ..." chain.

I have a few idea(s):

 - each method has a parameter that if set will let it know it's at the end (maybe it can 
   loop through all parameters to find if a special constant was passed?) eg
   $object->do()->something(now);

You can achieve an effect of an idea you suggested by overloading __call() method. This way you can abstract your parameter checking logic nicely.

But be aware, that such behaviour is not really obvious architecture-wise and you probably should consider PDO-like approach, that is using "finalizing" method like perform() or do() which, I think, you already have.

In Perl's DBIX::Class they trigger the query upon using the data. So each query chain doesn't return the database object but a DBIX::Class object that wraps around the database table object.

You can possibly try to implement a similar system. So, for example the following:

$resultSet = $database->select()->from('mytable');

does not actually return the table but a resultSet object. And only when you try to do:

$resultSet->next();

will the library actually fetch the data from the database.

Basically, all the accessor methods like first(), last(), all() and next() fetch and cache the data. If the data has already been fetched and is cached in memory then further calls to accessor methods on the resultSet object should just read the hash in memory instead of connecting to the database.


Additional detail:

Inserts don't have complex clauses like select. So the inserting function doesn't need to be chainable. So for inserts, you can simply have the API do something like this:

$database->insert()->into('mytable')->values({
    foo => "hello",
    bar => "world"
});

where the values() method act as the inserting function and triggers the database query.

Updates can simply be done on the row object. You can have the API do something like this:

$row = $database->select()->from('mytable')->first();
$row->foo('goodbye');
$row->update();

or alternatively:

$row->update({
    foo => "goodbye"
});

And have the update() method trigger the database query.