So, I have an existing class hierarchy that I can't modify. There are existing consumers of classes in that hierarchy in more than just my codebase. I have another class (in a new, but external library) that has a different contract (class prototype) with similar but improved functionality. I wish to provide that new functionality to existing consumers of the old code.
class OldBase {}
class OldSubClass extends OldBase{}
class NewCode {}
//consumers
existingMethod(OldSubClass $c) {}
alsoExistingMethod(OldBase $c) {}
I thought of using an AdapterInterface, but this seems, perhaps, inelegant.
interface NewCodeAdapterInterface
{
//interface that mimics the contract from OldBase
}
class NewCodeImplementation implements NewCodeAdapterInterface{}
//now this code can not be used with any existing OldBase objects :-\
existingMethod(NewCodeAdapterInterface $c) {}
I'd like to ensure a backwards compatible way to allow old code to be used while allowing a clean way to use the new with as few ramifications as possible, but how?
Starting with the premise that you want to implement a unified replacement of disparate classes consumed by existing code, without modifying the existing consumers, then I have a... "solution".
Here's an example of the current problem:
class A
{
public function test()
{
echo "A\n";
}
}
class B
{
public function test()
{
echo "B\n";
}
}
class Consumer
{
public function runTestA(A $a)
{
$a->test();
}
public function runTestB(B $b)
{
$b->test();
}
}
$con = new Consumer();
$a = new A();
$b = new B();
$con->runTestA($a);
$con->runTestB($b);
You're trying to find a solution that will allow something like, without modifying anything in Consumer:
$con = new Consumer();
$c = new C();
$con->runTestA($c);
$con->runTestB($c);
I'm going to heavily advise against doing what I'm about to outline. It would be better to modify the method signatures in Consumer to allow a new class to be passed that has the joint functionality. But, I'm going to answer the question as asked...
To start with, we need a couple of classes which can pass any existing method signatures. I'll use a trait to define the joint functionality.
trait ExtensionTrait
{
public function test()
{
echo "New Functionality\n";
}
}
class ExtendedA extends A
{
use ExtensionTrait;
}
class ExtendedB extends B
{
use ExtensionTrait;
}
Now we have some classes with the new functionality, which can pass the method checks... if we pass the right one. So, how do we do that?
Let's first put together a quick utility class that allows easy switching between the two classes.
class ModeSwitcher
{
private $a;
private $b;
public $mode;
public function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
$this->mode = $this->a;
}
public function switchMode()
{
if ($this->mode instanceof ExtendedA)
{
$this->mode = $this->b;
}
elseif ($this->mode instanceof ExtendedB)
{
$this->mode = $this->a;
}
}
public function __set($name, $value)
{
$this->a->$name = $value;
$this->b->$name = $value;
}
public function __isset($name)
{
return isset($this->mode->$name);
}
public function __unset($name)
{
unset($this->a->$name);
unset($this->b->$name);
}
public function __call($meth, $args)
{
return call_user_func_array([$this->mode, $meth], $args);
}
}
This mode switcher class maintains a current mode class, which passes through gets and calls. Sets and unsets are applied to both classes, so any properties modified aren't lost upon a mode switch.
Now, if we can modify the consumer of the consumer, we can put together a translation layer that automatically switches between modes to find the correct mode.
class ConsumerTranslator
{
private $consumer;
public function __construct(Consumer $consumer)
{
$this->consumer = $consumer;
}
public function __get($name)
{
return $this->consumer->$name;
}
public function __set($name, $value)
{
$this->consumer->$name = $value;
}
public function __isset($name)
{
return isset($this->consumer->$name);
}
public function __unset($name)
{
unset($this->consumer->$name);
}
public function __call($methName, $arguments)
{
try
{
$tempArgs = $arguments;
foreach ($tempArgs as $i => $arg)
{
if ($arg instanceof ModeSwitcher)
{
$tempArgs[$i] = $arg->mode;
}
}
return call_user_func_array([$this->consumer, $methName], $tempArgs);
}
catch (\TypeError $e)
{
$tempArgs = $arguments;
foreach ($tempArgs as $i => $arg)
{
if ($arg instanceof ModeSwitcher)
{
$arg->switchMode();
$tempArgs[$i] = $arg->mode;
}
}
return call_user_func_array([$this->consumer, $methName], $tempArgs);
}
}
}
Then, we can use the combined functionality like so:
$con = new Consumer();
$t = new ConsumerTranslator($con);
$a = new ExtendedA();
$b = new ExtendedB();
$m = new ModeSwitcher($a, $b);
$t->runTestA($m);
$t->runTestB($m);
This allows you to interchangeably utilize either class tree without any modification of Consumer whatsoever, nor any major changes to the usage profile of Consumer, as the Translator is basically a passthrough wrapper.
It works by catching the TypeError thrown by a signature mismatch, switching to the paired class, and trying again.
This is... not recommended to actually implement. The constraints declared provided an interesting puzzle, though, so, here we are.
TL;DR: Don't bother with any of this mess, just modify the consuming contract and use a joint interface, like you were intending.
Related
Let us imagine, we have following declaration of interface.
<?php
namespace App\Sample;
interface A
{
public function doSomething();
}
and class B that implements interface A.
<?php
namespace App\Sample;
class B implements A
{
public function doSomething()
{
//do something
}
public function doBOnlyThing()
{
//do thing that specific to B
}
}
Class C will depends on interface A.
<?php
namespace App\Sample;
class C
{
private $a;
public function __construct(A $a)
{
$this->a = $a;
}
public function doManyThing()
{
//this call is OK
$this->a->doSomething();
//if $this->a is instance of B,
//PHP does allow following call
//how to prevent this?
$this->a->doBOnlyThing();
}
}
...
(new C(new B()))->doManyThing();
If instance class B is passed to C, PHP does allow call to any public methods of B even though we typehint constructor to accept A interface only.
How can I prevent this with the help of PHP, instead of relying on any team members to adhere interface specification?
Update : Let us assume I can not make doBOnlyThing() method private as it is required in other place or it is part of third-party library that I can not change.
You can't do it in PHP, as it doesn't prevent this type of method calling.
You can prevent it by using tools like PHPStan to detect method calls on parameters that aren't guaranteed to be there.
In almost any language there are features in the language that theoretically could be used, but the people in charge of a team of programmers choose to not allow those features to be how the team should be writing code.
Using static analysis tools, and other code quality tools are usually the best way to enforce these rules. Preferably on a pre-commit hook if you can set these up, otherwise in your automated build tools after a commit has been made.
This proxy class will throw an exception when using other methods than specified interface:
class RestrictInterfaceProxy
{
private $subject;
private $interface;
private $interface_methods;
function __construct($subject, $interface)
{
$this->subject = $subject;
$this->interface = $interface;
$this->interface_methods = get_class_methods($interface);
}
public function __call($method, $args)
{
if (in_array($method, $this->interface_methods)) {
return call_user_func([$this->subject, $method], $args);
} else {
$class = get_class($this->subject);
$interface = $this->interface;
throw new \BadMethodCallException("Method <b>$method</b> from <b>$class</b> class is not part of <b>$interface</b> interface");
}
}
}
You should then change your C constructor:
class C
{
private $a;
public function __construct(A $a)
{
// Just send the interface name as 2nd parameter
$this->a = new RestrictInterfaceProxy($a, 'A');
}
public function doManyThing()
{
$this->a->doSomething();
$this->a->doBOnlyThing();
}
}
Testing:
try {
(new C(new B()))->doManyThing();
} catch (\Exception $e) {
die($e->getMessage());
}
Output:
Method doBOnlyThing from B class is not part of A interface
Previous answer: I misunderstood OP's question. This class will throw an exception if a class has methods that none of the interface it implements has.
Use it as $proxified = new InterfaceProxy(new Foo);
class InterfaceProxy
{
private $subject;
/* In PHP 7.2+ you should typehint object
see http://php.net/manual/en/migration72.new-features.php */
function __construct($subject)
{
$this->subject = $subject;
// Here, check if $subject is complying
$this->respectInterfaces();
}
// Calls your object methods
public function __call($method, $args)
{
if (is_callable([$this->subject, $method])) {
return call_user_func([$this->subject, $method], $args);
} else {
$class = get_class($this->subject);
throw new \BadMethodCallException("No callable method $method at $class class");
}
}
private function respectInterfaces() : void
{
// List all the implemented interfaces methods
$interface_methods = [];
foreach(class_implements($this->subject) as $interface) {
$interface_methods = array_merge($interface_methods, get_class_methods($interface));
}
// Throw an Exception if the object has extra methods
$class_methods = get_class_methods($this->subject);
if (!empty(array_diff($class_methods, $interface_methods))) {
throw new \Exception('Class <b>' . get_class($this->subject) . '</b> is not respecting its interfaces', 1);
}
}
}
I took help on the following answers:
SO - php get interface methods
How to auto call function in php for every other function call
Of course this solution is custom but as PHP won't solve this issue by itself I thought it would worth giving a try to build this myself.
I need to organize some kind of access control to object methods when it is used in different contexts (API's in my system). Here is code example:
class A
{
public function doA(){}
public function doB(){}
}
class APIAClient
{
public function getA()
{
return new A();
}
}
class APIBClient {
public function getA()
{
return new A();
}
}
In APIAClient object A should have both methods doA() and doB(), but in APIBClient should not have doB() method.
For now I've implemented APIBClientAProxy (which is returned by APIBCleint->getA())
class APIBClientAProxy
{
private $a = new A;
public function doA()
{
$this->a->doA()
}
}
But may be there is a better pattern for solving my problem, without using a additional proxy object for every context (i.e. API). I'm thinking about magic __call method with list of allowed methods in particular context, but magic calls is hard do document and documentation is the big point in my app (API's should be documented well)
Thanks!
Instead of inheritance you can use composition through traits (introduced in PHP 5.4).
First define traits
trait A {
public function doA() {
// do something here
}
}
trait B {
public function doB() {
// do something here
}
}
then use those traits in your class declaration
class APIAClient {
use A, B
}
class APIBClient {
use A
}
You could use inheritance here, like this:
class A {
public function doA() {
// do something here
}
}
class B extends A {
public function doB() {
// do something here
}
}
class APIAClient
{
public function getObj() {
return new B();
}
}
class APIBClient {
public function getObj() {
return new A();
}
}
This way, when you call getObj() on APIAClient, it will return an instance of B which which has both doA() and doB(). However, when you call it on APIBClient, you return an instance of A which only has doA().
You can't change the class depending on when and how it's instances are created (well, not really). You could use a hacky workaround (but I'd recommend against it)
class A
{
private $_canDoB = null;
public function __construct($doB = true)
{
$this->_canDoB = !!$doB;//force bool
}
public function doB()
{
if ($this->_canDoB === false)
{
throw new LogicError('You can\'t doB');
}
}
}
So if you pass a falsy value to the constructor of A(in your APIBClient), doB will throw an error. However, I'd recommend using inheritance, too:
class AB
{
public function doA()
{
//both B and B share this method
}
}
class B
{//nothing atm
}
class A
{
public function doB()
}
And have your APIAClient return a new A(), whereas APIBClient returns a new instance of the B class.When using type-hinting, you can just check for AB instances:
public function doSomething(AB $instance)
{
if ($instance instanceof A)
{
return $instance->doB();
}
return $instance->doA();
}
Or, when not relying on type-hinting and type-checking, you can always use one of the many functions like method_exists
I have an existent class and I want to create a system to load "plugins" for it. Those "plugins" are created as files and then included in the file with the main class.
Now I think the main class needs to extend those little "plugins" with their own classes. The problem is that I don't know what plugins will include different users. So the extending of the classes is dynamically.
How can I extend on-the-fly a class, maybe without using eval (I didn't tested that either)?
Are you talking about __autoload?
function __autoload($class) {
require_once($class.'.php');
}
$object = new Something();
This will try to require_once(Something.php);
You can sort of do it by using PHP's magic functions. Suppose you have class A. You want an "instance" of A with some extra methods available.
class A {
public $publicA;
public function doA() {
$this->publicA = "Apple";
}
}
class B {
public $publicB;
public function doB() {
$this->publicB = "Bingo";
}
private $object;
public function __construct($object) {
$this->object = $object;
}
public function __call($name, $arguments) {
return call_user_func_array(array($this->object, $name), $arguments);
}
public function __get($name) {
return $this->object->$name;
}
public function __set($name, $value) {
$this->object->$name = $value;
}
}
$b = new B(new A);
$b->doA();
$b->doB();
echo "$b->publicA, $b->publicB";
The instance $b has the methods and properties of both A and B. This technique can be extended to aggregate functionalities from any number of classes.
What would be the best practice to do this :
class AwesomeClass {
// Code
public function test()
{
foreach($objects->values as $v)
{
New SuperClass($v);
}
return $objects;
}
}
class SuperClass {
public function __construct($arg2)
{
return trim($arg2);
}
}
$rule_the_world = New AwesomeClass($arg1);
$king = $rule_the_world->test();
The previous code is obviously not working, I think I'm missing some major point of PHP OO.
It's very difficult to decipher what you're asking for, and the code you have is not recoverable.
Code Errors
There are several errors in your code that are illogical:
AwesomeClass has no constructor.
This makes passing arg1 to new AwesomeClass meaningless
arg1 is never initialized
In AwesomeClass::test(), objects is never initialized and has no member value.
You will get a warning since it's not traversable
New SuperClass (should be new, per standards) does nothing.
__construct() cannot return a value.
What You May Want
What I think you're going for is something like this:
class AwesomeClass implements IteratorAggregate {
private $arg1;
public function __construct(array $arg1) {
$this->arg1 = $arg1;
}
public function getIterator() {
return new ArrayIterator($this->arg1);
}
}
class SuperClass {
private $arg2;
public function __construct($arg2) {
$this->arg2 = $arg2;
}
public function __toString() {
return "$this->arg2\n";
}
}
$rule_the_world = new AwesomeClass(array('one', 'two', 'three'));
foreach ($rule_the_world as $sc) {
$sc = new SuperClass($sc);
echo $sc;
}
Note that it is redundant to create an ArrayIterator instance when arg1 must already be an array, this is just an example.
I'm using WordPress as a CMS, and I want to extend one of its classes without having to inherit from another class; i.e. I simply want to "add" more methods to that class:
class A {
function do_a() {
echo 'a';
}
}
then:
function insert_this_function_into_class_A() {
echo 'b';
}
(some way of inserting the latter into A class)
and:
A::insert_this_function_into_class_A(); # b
Is this even possible in tenacious PHP?
If you only need to access the Public API of the class, you can use a Decorator:
class SomeClassDecorator
{
protected $_instance;
public function myMethod() {
return strtoupper( $this->_instance->someMethod() );
}
public function __construct(SomeClass $instance) {
$this->_instance = $instance;
}
public function __call($method, $args) {
return call_user_func_array(array($this->_instance, $method), $args);
}
public function __get($key) {
return $this->_instance->$key;
}
public function __set($key, $val) {
return $this->_instance->$key = $val;
}
// can implement additional (magic) methods here ...
}
Then wrap the instance of SomeClass:
$decorator = new SomeClassDecorator(new SomeClass);
$decorator->foo = 'bar'; // sets $foo in SomeClass instance
echo $decorator->foo; // returns 'bar'
echo $decorator->someMethod(); // forwards call to SomeClass instance
echo $decorator->myMethod(); // calls my custom methods in Decorator
If you need to have access to the protected API, you have to use inheritance. If you need to access the private API, you have to modify the class files. While the inheritance approach is fine, modifiying the class files might get you into trouble when updating (you will lose any patches made). But both is more feasible than using runkit.
An updated way for 2014 that copes with scope.
public function __call($method, $arguments) {
return call_user_func_array(Closure::bind($this->$method, $this, get_called_class()), $arguments);
}
Eg:
class stdObject {
public function __call($method, $arguments) {
return call_user_func_array(Closure::bind($this->$method, $this, get_called_class()), $arguments);
}
}
$obj = new stdObject();
$obj->test = function() {
echo "<pre>" . print_r($this, true) . "</pre>";
};
$obj->test();
You can use the runkit extension for this, but you should really consider regular inheritance instead.
See runkit_method_add.
No you can't dynamically change a class during runtime in PHP.
You can accomplish this by either extending the class using regular inheritance:
class Fancy extends NotSoFancy
{
public function whatMakesItFancy() //can also be private/protected of course
{
//
}
}
Or you could edit the Wordpress source files.
I'd prefer the inheritance way. It's a lot easier to handle in the long run.