Pregunta

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.

¿Fue útil?

Solución

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.

Otros consejos

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

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

?>

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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top