views:

143

answers:

4

i just got some more questions while learning PHP, does php implement any built in plugin system?

so the plugin would be able to change the behavior of the core component.

for example something like this works:

include 'core.class.php';
include 'plugin1.class.php';
include 'plugin2.class.php';
new plugin2;

where core.class.php contains

class core {
  public function coremethod1(){
    echo 'coremethod1';
  }
  public function coremethod2(){
    echo 'coremethod2';
  }
}

plugin1.class.php contains

class plugin1 extends core {
  public function coremethod1(){
    echo 'plugin1method1';
  }
}

plugin2.class.php contains

class plugin2 extends plugin1 {
  public function coremethod2(){
    echo 'plugin2method2';
  }
}

This would be ideal, if not for the problem that now the plugins are dependable on each other, and removing one of the plugins:

include 'core.class.php';
//include 'plugin1.class.php';
include 'plugin2.class.php';
new plugin2;

breaks the whole thing...

are there any proper methods to doing this? if there are not, them i might consider moving to a different langauge that supports this...

thanks for any help.

edit: obviously it is my understanding that is lacking, so here is a attempt at a clarification.

core.class.php contains anything...

plugin1.class.php contains anything...

plugin2.class.php contains anything...

include 'core.class.php';
include 'plugin1.class.php';
include 'plugin2.class.php';
$core = new core;
$core->coremethod1();//outputs plugin2method1

whereas:

include 'core.class.php';
include 'plugin2.class.php';
$core = new core;
$core->coremethod1();//outputs plugin1method1

I'm interested in any implementation, even one not involving classes for example

include 'core.php';
//does core stuff

include 'core.php';
include 'plugin1';
//does extended core stuff

include 'core.php';
include 'plugin2';
//does extended core stuff


include 'core.php';
include 'plugin2';
include 'plugin1';
//does very extended core stuff

including a file needs to change the application behavior. for it to have any meaning.

I do not know what this is called either, so point me at the proper naming if there is any.

+2  A: 

PHP core can be extended with PECL extensions (which are C++, I believe).

Core functions can be overridden (if you have the APD PECL extension installed) with override_function

User functions can be executed with call_user_func.

Maybe if you could explain what you are planning, we'd be able to offer a better answer?

adam
I tried to my best ability to explain what i am trying, i am trying to have a object, that does things, and plugins for this object, to make it do different things from the same object instance.
YuriKolovsky
As Peter Bailey says, a core extension modifies the actual php program. What you're probably after is a class extension - http://uk3.php.net/manual/en/keyword.extends.php
adam
+6  A: 

You are misusing the term "plugin". A plugin is generally a package of code that extends or alters the base functionality of a system - to make actual PHP plugins (which in the PHP world are called extensions) you'd be writing C or C++.

What you're describing here is merely including classes or class trees into the current execution for usage. And there is a way to bring them into the current execution context 'automatically', and that's via the autoload system.

If, after you've read the documentation on autoloading, you are still unsure of how to move forward, comment here and I will help you along.

EDIT

Ok, I see what you're after. You can't do exactly what you're after. When you execute new core; an instance of the class core will be returned - you can't modify that at all.

However, if you are willing to modify how you create instances of core, then I think I have something that could work for you, and it might look something like this.

class core {
  public function coremethod1(){
    echo 'coremethod1';
  }
  public function coremethod2(){
    echo 'coremethod2';
  }

  /**
   * @return core
   */
  final public static function create()
  {
    // listed in order of preference
    $plugins = array( 'plugin2', 'plugin1' );

    foreach ( $plugins as $plugin )
    {
        if ( class_exists( $plugin ) )
        {
          return new $plugin();
        }
    }

    return new self;
  }
}

class plugin1 extends core {
  public function coremethod1(){
    echo 'plugin1method1';
  }
}

class plugin2 extends plugin1 {
  public function coremethod2(){
    echo 'plugin2method2';
  }
}

$core = core::create();

// test what we have
echo get_class( $core ), '<br>'
   , $core->coremethod1(), '<br>'
   , $core->coremethod2()
;
Peter Bailey
thank you for the correction, that's why i typed out "class" plugins, but i guess its still incorrect. now I know how autoloading works, its a function that gets invoked if a created class does not exist.the problem is that how is invoking a non existing class going to allow me to overwrite the base functionality of a object instance?
YuriKolovsky
The base functionality of what? The inheritance pattern already does that for you. I'm not sure what you're after at this point. Are you unsure of how inheritance and subclasses work?
Peter Bailey
I didn't downvote, but this really should not be done this way. It probably works, but including a file should not determine how a class or the application behaves. If anything, use the Builder pattern or an Abstract Factory to create a composite or decorated object from the Core class, but don't use inheritance for this. The *plugins* should not be subclasses of Core. The way it is now, Plugin2 *is a* Plugin1 *is a* Core. You get a monolitic class and had to rewrite the plugins for each and every other base class. It's cool you found a way to do it, but this should not be used in my opinion.
Gordon
this is also interesting, closest to what i wanted :)but i think i'm still missing some key point, or my logic is just reversed.
YuriKolovsky
Gordon makes a valid point, i still cannot remove the plugin without breaking the functionality.
YuriKolovsky
@Yuri You *can* remove the plugin. If you use autoloading, removing plugin1 and only including plugin2 will include plugin1 when plugin2 is included. What won't work is removing plugin1 and only using plugin2, e.g. only overriding coremethod2. And PHP has no polymorphism via extend. But my point was actually much more directed towards using proper object orientied design than a hackish approach where a file inclusion effectively creates a class behavior and type.
Gordon
@Gordon just for the record, I 100% agree with you. At no offense to Yuri, I just find teaching OOD in a SO answer to be pretty much impossible.
Peter Bailey
@Peter I thought so. Just felt I had to voice my concerns before someone adopts this, loses his job and by strange fate and cosmic conincidence makes me the poor fellow to have to work on the legacy code then ;)
Gordon
@Gordon ahaha, lol
Peter Bailey
A: 

Your code is breaking because plugin2 extends plugin1, and you're not including the plugin1 class. Why not make class plugin2 extend core? That seems to be what you're going for.

Jage
this would be the closest thing, but then the object does not have the plugin1 functionality :P
YuriKolovsky
+3  A: 

If your only concern is that not including plugin1 will create an error, then you can resort to autoloading to have plugin2 load plugin1 automatically:

From the comments in the PHP Manual on spl_autoload

// Your custom class dir
define('CLASS_DIR', 'class/')

// Add your class dir to include path
set_include_path(get_include_path().PATH_SEPARATOR.CLASS_DIR);

// You can use this trick to make autoloader look 
// for commonly used "My.class.php" type filenames
spl_autoload_extensions('.class.php');

// Use default autoload implementation
spl_autoload_register();

If, however, you are looking for a traits/mixin-like feature, then the answer is no. PHP does not support this as of now. At least not without patching the core or resorting to these two APIs you do not want to use in production code.

The proper way to change how an object behaves at runtime would be to use Decorators:

$class = new BasicCache( new BasicValidators ( new Basic ) );

or Strategy patterns:

$class = new Basic;
$class->setStrategy(function() { return 'foo'} );
echo $class->callStrategy(); // foo
$class->setStrategy(function() { return 'bar'} );
echo $class->callStrategy(); // bar

See http://sourcemaking.com/design_patterns for the most common patterns.


EDIT Here is an example of how to create plugins with decorators. Assume, we have a game of some sort where some non-player characters walk around in a virtual space and greet the main character from time to time. That's all they do right now. We want some variation on how they greet though, which is why we need our plugins/decorators in this scenario.

First we create an interface that defines some methods any object able to greet should have. We don't care about what it does when these methods are invoked on a specific object. We just want to make sure that the methods are available and that they are called with a clearly defined input:

interface GreetInterface
{
    public function greet($name);
    public function setGreeting($greeting);
}

An interface is basically a contract any implementing object must fulfill. In our case, the contract says, if you are an object that can greet, you have to have two methods. Implement them any way you like, but have these methods.

Let's build our non-player character classes now, implementing this interface

class Dude implements GreetInterface
{
    protected $greeting = 'hello';
    public function greet($name)
    {
        return sprintf('%s %s', $this->greeting, $name);
    }
    public function setGreeting($greeting)
    {
        $this->greeting = $greeting;
        return $this;
    }
}

That's pretty straigtforward I guess. The Dude class just defines the two methods from the interface. When greet() is called, it will fetch the string stored in greeting and prepend to the param passed to the greet method. The setGreeting method allows us to change the greeting at runtime. Note: you could add a getter as well (I was just lazy)

Now on to the plugins. We will create an abstract GreetPlugin class to contain some shared boilerplate code, simply because we don't want to duplicate code in our actual plugins. The abstract plugin class will implement the GreetInterface, so we can make sure all subclasses implement the interface too.

Since Dude already implements the interface as well, we could have the plugins extend Dude, but that would be conceptually wrong, because extending creates an is-a relationship, but a plugin is not a Dude.

abstract class GreetPluginAbstract implements GreetInterface
{
    protected $inner;
    public function __construct(GreetInterface $inner)
    {
         $this->inner = $inner;
    }
    public function setGreeting($greeting)
    {
        $this->inner->setGreeting($greeting);
        return $this;
    }
    public function greet($name)
    {
        return $this->inner->greet($name);
    }
}

The plugin class accepts one argument when initialized: any class implementing the GreetInterface. The TypeHint makes sure, the class fulfills the contract. That's required, because, as you can see in the code, our plugins will need to call the methods in the interface on the class passed through the constructor. If we had extended from Dude, we would now be able to wrap dudes into dudes, which is a bit odd. Another reason for not doing it.

Now on to the first plugin. We want some of our dudes to speak with a fancy french accent, which means they use âccénts all over the place, but cannot pronounce a proper h. Disclaimer: yes, I know that's a cliche. Please bear with my examples

class FrenchPlugin extends GreetPluginAbstract
{
    public function greet($name) {
       return str_replace(array('h', 'e'), array('', 'é'),
                          $this->inner->greet($name));
    }
}

Since the Plugin extends the abstract plugin, we can now focus on the actual code that modifies how a regular dude would do his greeting. When greet() is called, we call greet() on the wrapped element and then remove all h characters and turn all es into és. Everything else is unmodified abstract behavior.

In another plugin, we want to change the wording of the greeting, so we have some dudes say Heya, instead of just Hello. Just to add some variation.

class EasyGoingPlugin extends GreetPluginAbstract
{
    protected $inner;
    public function __construct(GreetInterface $inner) {
         $this->inner = $inner->setGreeting('heya');
         parent::__construct($inner);
    }
}

This way we only override the constructor, because the greet method should just return whatever it will be. So we call the setGreeting method on the object passed to this plugin. Because the object has to implement the GreetInterface, we can be sure this works.

Note that I am assigning the return value of setGreeting as the inner object. This is possible because I return $this, whenever setMethod is called. This cannot be enforced through the interface, so you cannot rely on this form the interface. I just added it to show another technique: method chaining.

With two plugins done, we feel we have enough variation. Now we only need a convenient way to create Dudes. For that we create a small class like this:

class DudeBuilder
{
     public static function build()
     {
         $dude = new Dude();
         $decorators = func_get_args();
         foreach($decorators as $decorator) {
             $decorator .= "Plugin";
             // require_once $decorator;
             $dude = new $decorator($dude);
         }
         return $dude;
     }
}

Note: I always mix up Builder and AbstractFactory, so if the above is a Factory, well, then it's a factory. Check out the design patterns links I gave earlier on ;)

All this Builder does, is create a regular dude and then wrap/decorate it into/with whatever plugins we tell it to use and than return it. Because the builder encapsulates no own state, we make the build method static.

For this example I assume you used the autoloading code I gave right on top. If not, you can include the plugin files in the foreach loop. Lazy loading them only when they are needed will give you a few microseconds faster load times over including them all on top. Hopefully, this also explains what I meant in the various comments when I argued the behavior should not be controlled by a file inclusion. The file inclusion is just a necessity. You cannot use a class that is not know to PHP. But that the class is actually used, is controlled by our code alone, by passing in the plugin names to the build method.

Let's do this now

$regularDude         = DudeBuilder::build();
$frenchDude          = DudeBuilder::build('French');
$easygoingDude       = DudeBuilder::build('EasyGoing');
$frenchEasyGoingDude = DudeBuilder::build('French', 'EasyGoing');

This is effectively the same as doing:

$regularDude         = new Dude;
$frenchDude          = new FrenchPlugin(new Dude);
$easygoingDude       = new EasyGoingPlugin(new Dude);
$frenchEasyGoingDude = new FrenchPlugin(new EasyGoingPlugin(new Dude));

With just two plugins, we can now create three types of Dudes. Let's have them greet you:

echo $regularDude->greet('Yuri'), PHP_EOL,
     $frenchDude->greet('Yuri'), PHP_EOL,
     $easygoingDude->greet('Yuri'), PHP_EOL,
     $frenchEasyGoingDude->greet('Yuri'), PHP_EOL;

// gives

hello Yuri
éllo Yuri
heya Yuri
éya Yuri

We can now create additional plugins to decorate our basic classes with. If for some reason, you decide your game should have talking horses or cars as well, you could also create a class Car or Horse and have it implement the greet interface too and add a Builder for them. You can then reuse the plugins to create French EasyGoing Cars or Horses.

Gordon
my only concern is that i want to add functionality to classes using optional unlimited file includes.to be more specific, i want to know if this is possible in php
YuriKolovsky
@YuriKolovsky You shouldn't be thinking of functionality when refering to file includes when doing OOP programming. Including class files is just a necessity. It doesn't add functionality. The proper OOP way of changing behavior at runtime is through appropriate design patterns. Please see the linked pages.
Gordon
I have tried decorators, and overloading and even the hated eval, but i feel like i'm missing something here, so i will check out your design patters link.
YuriKolovsky
If including files to change functionality is wrong, and requesting files for missing functionality is right, then how does the application determine what it needs to load?
YuriKolovsky
@Yuri Requesting isn't wrong. It's okay to use `include` or to setup the autoloader, but you have to understand that a file just holds a class definition. Nothing more. It's not supposed to initialize or override something just by the fact that it is included. That's behavior you create inside your application. See the update.
Gordon
excellent excellent excellent!! tested it and it does indeed do what i was asking for.now a important question, should I use this in production level, or is there something that I should first learn and do it in some other manner or is this method completely valid?
YuriKolovsky
@Yuri If you have little experience with design patterns, I suggest to read up on them to learn when to use which. Maybe check out the other answer I linked for strategy as well for a different approach. But basically, you can use it like this.
Gordon
thanks, i will definitely read up on design patterns.
YuriKolovsky
thanks for the detailed update!
YuriKolovsky