0

In PHP using method chaining how would one go about supplying a functional call after the last method being called in the chain?

Also while using the same instance (see below). This would kill the idea of implementing a destructor.

The end result is a return value and functional call of private "insert()" from the defined chain properties (of course) without having to call it publicly, no matter of the order.

Note, if I echo (__toString) the methods together it would retrieve the final generated unique code which is normal behavior of casting a string.

Example below:

class object
{
    private $data;
    function __construct($name) {
        // ... some other code stuff
    }

    private function fc($num) {
        // some wicked code here
    }

    public function green($num) {
        $this->data .= fc($num*10);
        return $this;
    }
    public function red($num) {
        $this->data .= fc($num*25);
        return $this;
    }
    public function blue($num) {
        $this->data .= fc($num*1);
        return $this;
    }

    // how to get this baby to fire ?
   private function insert() {
          // inserting
          file_put_content('test_code.txt', $this->data);
   }
}

$tss = new object('index_elements');

$tss->blue(100)->green(200)->red(100);       // chain 1
$tss->green(0)->red(100)->blue(0);           // chain 2
$tss->blue(10)->red(80)->blue(10)->green(0); // chain 3

Chain 1, 2, and 3 would generated an unique code given all the values from the methods and supply an action, e.g. automatically inserting in DB or creating a file (used in this example).

As you can see no string setting or casting or echoing is taking place.

5
  • What/where is chain 1 and 2? Commented Apr 17, 2014 at 17:03
  • You want to run something after three calls to methods? After all colors are set? You want another method with a callback? After PHP magically found out when you want to run the code? Also how does a destructor come into play in all of this? Commented Apr 17, 2014 at 17:05
  • @AmalMurali scroll down the code to the end. Commented Apr 17, 2014 at 17:07
  • @PeeHaa correct! A final callback method that contains all the data appended from the three method calls. Commented Apr 17, 2014 at 17:09
  • 3
    So what is stopping you from adding that method? Commented Apr 17, 2014 at 17:10

4 Answers 4

3

You could keep a list of things that needs to be initialised and whether they have been so in this instance or not. Then check the list each time you use one of the initialisation methods. Something like:

class O {
    private $init = array
        ( 'red' => false
        , 'green' => false
        , 'blue' => false
        );

    private function isInit() {
        $fin = true;
        foreach($this->init as $in) {
            $fin = $fin && $in;
        }
        return $fin;
    }

    public function green($n) {
        $this->init['green'] = true;
        if($this->isInit()) {
            $this->insert();
        }
    }

    public function red($n) {
        $this->init['red'] = true;
        if($this->isInit()) {
            $this->insert();
        }
    }

    public function blue($n) {
        $this->init['blue'] = true;
        if($this->isInit()) {
            $this->insert();
        }
    }

    private function insert() {
        echo "whee\n";
    }
}

But personally I think this would be more hassle then it's worth. Better imo to expose your insert method and let the user of you code tell when the initialisation is finished. So something that should be used like:

$o->red(1)->green(2)->blue(0)->insert();

-update-

If it's the case that it's impossible to predict what functions need to be called you really do need to be explicit about it. I can't see a way around that. The reason is that php really can't tell the difference between

$o1 = new A();
$o2 = $o1->stuff();

and

$o2 = (new A())->stuff();

In a language that allows overloading = I guess it would be possible but really really confusing and generally not a good idea.

It is possible to move the explicit part so that it's not at the end of the call chain, but I'm not sure if that would make you happier? It would also go against your desire to not use another instance. It could look something like this:

class O {
    public function __construct(InitO $ini) {
        // Do stuff
        echo "Whee\n";
    }
}

class InitO {
    public function red($n) {
        return $this;
    }
    public function green($n) {
        return $this;
    }
    public function blue($n) {
        return $this;
    }
}

$o = new O((new InitO())->red(10)->red(9)->green(7));

You can of course use just one instance by using some other way of wrapping but the only ways I can think of right now would look a lot uglier.

Sign up to request clarification or add additional context in comments.

4 Comments

Without publicly implementing the insert() :-p having it automatically call the insert method inside the class dynamically.
Yes, that was my first example. I just think it's redundant, but it works.
your isInit() works, of course. However, it requires to go through ALL three colors! What if one simply wants to go through 2 red() functions and 1 blue() function, and no green() function? Then the Boolean match will fail and the insert() will not be triggered..
Then it really will not work out. Note that you can't use a destructor either since it will run when all variables referring to the instance goes out of scope, not when the chain ends. The ending of the chain is only syntactic. It says nothing about what methods will be called later.
1

Im with PeeHaa, this makes no sense! :)

Only chance to have something magically happen after the last chain was used (without being able to look into the future) is a Destructor/Shutdown function OR a manually cast/call to insert()

Comments

0

You can also decide to implement this statically without using objects.

<?php
class Object
{
    private static $data;

    public static function set($name) 
    {
        // ... some other code stuff
    }

    private static function fc($num) 
    {
        // some wicked code here
    }

    public static function green($num) 
    {
        self::$data .= self::fc($num*10);
        return new static;
    }

    public static function red($num) 
    {
        self::$data .= self::fc($num*25);
        return new static;
    }

    public static function blue($num) {
        self::$data .= self::fc($num*1);
        return new static;
    }

    // how to get this baby to fire ?
    public static function insert() 
    {
       // inserting
       file_put_content('test_code.txt', self::$data);
    }

}

//$tss = new object('index_elements');

    $Object::set('index_elements')->blue(100)->green(200)->red(100)->insert();       // chain 1
    $Object::set('index_elements')->green(0)->red(100)->blue(0)->insert();           // chain 2
    $Object::set('index_elements')->blue(10)->red(80)->blue(10)->green(0)->insert(); // chain 3

?>

Comments

0

Ok let's see a code example

<?php

// map dummy class
class map
{
// __call magic method
public function __call($name, $args)
{
return $this;
}
}

// now we chain
$map = new map;

// let's find me

$map->start('here')
->go('right')
->then()
->turn('left')
->and('get water')
->dontEat()
->keep('going')
->youShouldSeeMe('smiling');

here we don't know what the last method would be and we need to trigger a kinda operation or event once we hit the end.

According to data structure we can call this the LIFO stack. (Last in first out)

so how did i solve this on PHP?

// i did some back tracing

... back to the __call function

function __call($name, $args)
{

$trace = debug_backtrace()[0];
$line = $trace['line'];
$file = $trace['file'];
$trace = null;
$getFile = file($file);
$file = null;
$getLine = trim($getFile[$line-1]);
$line = null;
$getFile = null;

$split = preg_split("/(->)($name)/", $getLine);
$getLine = null;

if (!preg_match('/[)](->)(\S)/', $split[1]) && preg_match('/[;]$/', $split[1]))
{
// last method called.
var_dump($name); // outputs: youShouldSeeMe
}

$split = null;

return $this;
}

And whoolla we can call anything once we hit the bottom. *(Notice i use null once i am done with a variable, i come from C family where we manage memory ourselves)

Hope it helps you one way or the other.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.