PHP链式类方法与其他类合并

I start with an example.

Mold class

<?php
class Mold {
  public $current;

  function merge($first, $second) {
    $this->current = $first . $second;
    return $this;
  }

  function phone() {
    $this->current = '<a href="tel:' . $this->current . '">' . $this->current . '</a>';
    return $this;
  }

  function __toString() {
    return $this->current;
  }
}

function mold() {
  return new Mold();
}

In action

This works as expected.

echo mold()->merge('sada', 'asdsa')->phone();

Problem

I have one or more classes with methods that wants to be available as well.

Plugin class

class MyPlugin {
  function link() {
    // Code
  }

  function currency($number) {
    // Code
  }
}
class MyPlugin2 {
  // Other methods
}

Dream code

The exact change of events below may not make any sense. Anyway, what I intend to do is the following.

echo mold()->merge('sada', 'asdsa')->currency(45)-link();
  • Call mold() which creates a new instance of the Mold class.
  • Chain merge() which is used from the Mold class.
  • currency() or link() method does not exist in the Mold class. Instead they should be loaded from one of the plugins.

In conclusion

  • I know I can extend a class but it does not really solve the problem because there can be more than one plugin classes.
  • I know I can create instances of the plugin classes, but they somehow need to be aware by the Mold class.
  • Append methods to a class comes to mind as well as merge classes.

Interesting ideas.

The one that solves the problem like I had in my mind was to use traits like @vivek_23 suggested in a comment.

Here is a complete example of how it works

<?php
// Core methods
class MoldCore {
  public $current;

  function merge($first, $second) {
    $this->current = $first . $second;
    return $this;
  }

  function phone() {
    $this->current = '<a href="tel:' . $this->current . '">' . $this->current . '</a>';
    return $this;
  }

  function __toString() {
    return $this->current;
  }
}

// Plugin 1
trait Plugin1 {
  public function yaay() {
    $this->current .= ' Plugin1 yaay';
    return $this;
  }
}

// Plugin 2
trait Plugin2 {
  public function hello() {
    $this->current = str_replace('Plugin1', 'Modified', $this->current);
    return $this;
  }

  public function world() {
    $this->current .= ' Plugin2 world';
    return $this;
  }
}

// Glue plugins with MoldCore
class Mold extends MoldCore {
  use Plugin1, Plugin2;
}

$mold = new Mold();
echo $mold->merge('sada', 'asdsa')->phone()->yaay()->hello();

A concept that you might try to apply is the Composition Over Inheritance.

A solution could be to compose your current Moldobject and pass the two plugins instances so that it could use them on its methods, like so:

<?php
class Mold {
  public $current;

  public function __construct(Plugin1 $plugin1, Plugin2 $plugin2) {
      $this->plugin1 = $plugin1;
      $this->plugin2 = $plugin2;
  }

  public function merge($first, $second) {
    $this->current = $first . $second;
    return $this;
  }

  public function phone() {
    $this->current = '<a href="tel:' . $this->current . '">' . $this->current . '</a>';
    return $this;
  }

  public function __toString() {
    return $this->current;
  }

  public function link() {
      $this->plugin1->link();
      return $this;
  }
}

function mold() {
  return new Mold(new Plugin1(), new Plugin2());
}

Another solution could be to create a MoldHandler class of some sort, which has a Mold object as a property together with the other plugins, which could be used inside of this directly.

public class MoldHanlder {

    protected $mold;
    protected $plugin1;
    protected $plugin2;

    public function __contruct($mold, $plugin1, $plugin2) {
        $this->mold    = $mold;
        $this->plugin1 = $plugin1;
        $this->plugin2 = $plugin2;
    }

    public function merge() {
        $this->mold = $this->mold->merge();
        return $this;
    }

    public function link() {
        $foo = $this->plugin1->method();

        return $this;
    }

    ...
}

You could also just instantiate the classes inside the construct, but if you are planning about writing unit tests, dependency injection is the way to go

I think that the first approach is way better because you avoid yourself to re-write some code of the Mold class, which is unnecessary

N.B. this is a really raw solution proposed just to let you better understand the idea I would opt for cheers

Technically you could use __call() for this. Your "main" class could contain/track a set of plugin instances and any unknown method call could be looked up and delegated, if available in a plugin.

I wouldn't recommend this though. Fluent APIs are often brittle and can get inconvenient even with one class. Involving multiple classes in fluent calls might quickly to add up to a confusing mess. A mess which even IDE can't help you (or another person working with code) with since it has no clue about all the magic happening.

I would highly recommend to look into alternative patterns for an API like this.