extending interfaces - fringe behavior

php.internals

Hans Lellelid

22 years ago
I have a need in a current application to extend interfaces and possibly re-define (change signature) some of the inherited methods in the child interface. e.g. interface Foo { public function __construct(MyCls $var); public function myFunction(); } interface Bar extends Foo { public function __construct(MyCls $var, $var2 = null); public function myOtherFunction(); } class A implements Bar { public function __construct(MyCls $var, $var2 = null) { // ... } public function myFunction() { // ... } public function myOtherFunction() { // ... } } This is not currently possible in PHP5b4, due to the (new?) "Can't inherit abstract function" errors generated by the engine. I.e. if I try to redefine __construct() I will get an error. The need for this capability is probably not that mainstream, but it'd be very nice. Is there any chance of having support for this behavior? I don't know anything about the underlying mechanism for supporting interface extension, so I don't know if what I'm asking is trivial or not. Thanks, Hans

Zeev Suraski

22 years ago
At 07:55 16/02/2004, Hans Lellelid wrote:
>I have a need in a current application to extend interfaces and possibly >re-define (change signature) some of the inherited methods in the child >interface. > >e.g. > >interface Foo { > public function __construct(MyCls $var); > public function myFunction(); >} > >interface Bar extends Foo { > public function __construct(MyCls $var, $var2 = null); > public function myOtherFunction(); >} > >class A implements Bar { > public function __construct(MyCls $var, $var2 = null) { > // ... > } > public function myFunction() { > // ... > } > public function myOtherFunction() { > // ... > } >} > >This is not currently possible in PHP5b4, due to the (new?) "Can't inherit >abstract function" errors generated by the engine. I.e. if I try to >redefine __construct() I will get an error. > >The need for this capability is probably not that mainstream, but it'd be >very nice. Is there any chance of having support for this behavior? I >don't know anything about the underlying mechanism for supporting >interface extension, so I don't know if what I'm asking is trivial or not.
You're not supposed to change the signature when you're extending/implementing interfaces. I'll try to see if it's feasible to improve the checks so that they compare signatures in a smarter way (i.e., func($arg1, $arg2=null) is compatible with func($arg1), and currently it's not detected that way). Zeev

Hans Lellelid

22 years ago
Hi - Zeev Suraski wrote:
> At 07:55 16/02/2004, Hans Lellelid wrote: > >> I have a need in a current application to extend interfaces and >> possibly re-define (change signature) some of the inherited methods in >> the child interface. > ... > > > You're not supposed to change the signature when you're > extending/implementing interfaces. I'll try to see if it's feasible to > improve the checks so that they compare signatures in a smarter way > (i.e., func($arg1, $arg2=null) is compatible with func($arg1), and > currently it's not detected that way). >
Yeah, I realize that wanting to change signatures of parent interfaces is a little weird; I'd submit that it does allow for some nice design possibilities -- but perhaps modifying an interface is at odds with the idea of an interface. I think I probably want to just copy & paste the methods that are the same rather than extend. Making it possible to re-define methods w/ additional null params would be nice if that makes sense. Thanks! Hans

Marcus Börger

22 years ago
Hello Hans, Monday, February 16, 2004, 10:07:53 PM, you wrote:
> Hi -
> Zeev Suraski wrote: >> At 07:55 16/02/2004, Hans Lellelid wrote: >> >>> I have a need in a current application to extend interfaces and >>> possibly re-define (change signature) some of the inherited methods in >>> the child interface. >> ... >> >> >> You're not supposed to change the signature when you're >> extending/implementing interfaces. I'll try to see if it's feasible to >> improve the checks so that they compare signatures in a smarter way >> (i.e., func($arg1, $arg2=null) is compatible with func($arg1), and >> currently it's not detected that way). >>
> Yeah, I realize that wanting to change signatures of parent interfaces > is a little weird; I'd submit that it does allow for some nice design > possibilities -- but perhaps modifying an interface is at odds with the > idea of an interface. I think I probably want to just copy & paste the > methods that are the same rather than extend.
> Making it possible to re-define methods w/ additional null params would > be nice if that makes sense.
In C++ overwritten default value of derived classes are ignored because the binding happens at compile time and the default values would have been evaluated at run time to work as expected. Now PHP is different. We would have the default values at runtim and compile time and the problem is only inheriting the methods. Currently NULL matches any time so it could work. Zeev maybe this is something worth to verify? Best regards, Marcus mailto:helly@php.net

Hans Lellelid

22 years ago
Hi - This issue of inheritance & interfaces persists to give me problems... Perhaps I'm going about this wrong for PHP.
> Zeev Suraski wrote: > >> >> You're not supposed to change the signature when you're >> extending/implementing interfaces. I'll try to see if it's feasible >> to improve the checks so that they compare signatures in a smarter way >> (i.e., func($arg1, $arg2=null) is compatible with func($arg1), and >> currently it's not detected that way).
Here's the problem. For background, I'm working on Creole, a JDBC-like DB abstraction API for PHP. This framework has both Statement and PreparedStatement classes. Originally I was using an abstract class model that I'm now moving to interfaces for added flexibility (ability to swap in decorator classes, etc.). I'm having a difficult time representing the relationship between these classes when using interfaces. The interfaces and class definitions are as follows: interface Statement { // methods } interface PreparedStatement { // methods, including some identical to Statement & some w/ same name // but different signature } class SQLiteStatement implements Statement { // impl } class SQLitePreparedStatement extends SQLiteStatement implements PreparedStatement { // impl } Originally, the PreparedStatement interface was extending the Statement interface, but this broke because several PreparedStatement methods have different signatures from their Statement counterparts -- hence my interest in allowing interfaces to re-define signatures. Now I am no longer using inheritance for the interfaces, *but* the problem now is that in SQLitePreparedStatement I am unable to inherit methods from SQLiteStatement that meet the interface requirements of PreparedStatement. E.g. -- both SQLiteStatement and SQLitePreparedStatement have identical setLimit() and setOffset() methods. I'd like to be able to inherit these from SQLiteStatement -- indeed there's a lot of code that's the same. I also need these methods to be specified in the PreparedStatement interface (since I can no longer using inheritance for that, I have to redefine these methods in PreparedStatement). I just get this when I try to inherit these functions: Fatal error: Can't inherit abstract function PreparedStatement::setLimit() (previously declared abstract in Statement) in /home/sandbox/creole2/classes/creole/drivers/sqlite/SQLitePreparedStatement.php on line 32 Any chance that the engine could be modified to allow inherited methods to meet interface requirements? (Again, extending the interfaces is not an option since some method signatures change & PHP won't allow this.) The only option I see currently is to duplicate all methods from SQLiteStatment in SQLitePreparedStatement, which will produce the correct end results but with a definite readability/maintainability price. To me the bottom line is that these classes are definitely related and that it's frustrating not to be able to express that in PHP. Perhaps there's a workaround that's just not occurring to me right now...? Thanks again! Hans

Andi Gutmans

22 years ago
It depends what you call different signature. If you are talking about default arguments, we should consider allowing those as they don't change the isA relationship. However, if you want us to allow inheriting interfaces which truly differ in function prototypes than that would definitely be a strong no. You can never break the isA relationship. Andi At 07:47 PM 2/16/2004 -0500, Hans Lellelid wrote:

Hans Lellelid

22 years ago
Andi Gutmans wrote:
> It depends what you call different signature. If you are talking about > default arguments, we should consider allowing those as they don't > change the isA relationship. However, if you want us to allow > inheriting interfaces which truly differ in function prototypes than > that would definitely be a strong no. You can never break the isA > relationship.
I think at the heart of the problem (well, my problem) is the fact that using interfaces is restricting the way that classes are allowed to inherit from other classes. -- Example (1) -- In more traditional PHP this works fine: class A { function init() { ... } function doSomething($arg1, $arg2) { ... } } class B extends A { function doSomething($arg1, $arg2, $arg3, $arg4 = null) { ... } function doSomethingElse($arg1) { ... } } $obj = new B; // $obj instanceof B : YES // $obj instanceof A : YES -- but misleading -- Example (2) -- Now when you throw interfaces into the mix, I won't be able to do that anymore -- even if I use two separate interfaces for class A and class B. interface IA { function init(); function doSomething($arg1,$arg2); } interface IB { funciton init(); function doSomething($arg1, $arg2, $arg3, $arg4 = null); function doSomethingElse($arg1); } class A implements IA { // same class body as above } class B extends A implements IB { // same class body as above } Now I get fatal errors, because class B is inheriting functionality from A that is required by the interface IB. Actually, I think what is happening (can someone confirm this?) is that class B is being forced to implement both IA and IB, and because interfaces (unlike classes) are not allowed to override methods class B cannot implement both interfaces. Posslble solutions to this (in PHP engine): - do not inherit interfaces: force "implements" to be explicit for each class, so that in this case class B would not implement interface IA. This doesn't seem like a particularly good solution, but it would work [I think]. - allow interfaces to override methods. I think to be consistent with PHP class behavior this is still the best option. Just like PHP classes can override methods, I think interfaces should be allowed. It's just as easy to break "instanceof" using class inheritance (as I showed in Example 1) as it would be to break it using interface overrides. In Java this isn't an issue of course because of overloading. Does anyone have any other suggestions for this? I agree that radically breaking signatures in subclasses is probably not "good practice" but in light of the fact that PHP doesn't support method overloading, I don't really see an alternative. My take on it is that this is just a limitation of PHP -- and I'm fine with that. I think to be consistent it would be good if interfaces supported the same behavior -- again, agreeing all the while that academically it's not the "best" design. Hans

Stephane Drouard

22 years ago
== Quote from Hans Lellelid (hans@appliedsec.com)'s article
> In more traditional PHP this works fine: > > class A { > function init() { ... } > function doSomething($arg1, $arg2) { ... } > } > > class B extends A { > function doSomething($arg1, $arg2, $arg3, $arg4 = null) { ... } > function doSomethingElse($arg1) { ... } > }
Even if PHP allows it, there is however a problem. Imagine you write code that assumes to have a A object. So you call doSomething() with only 2 arguments. If you now pass to this code a B object, you will have an error because $arg3 is not provided. So adding arguments to an overloaded method should only be done if the additionnal arguments have a default value. Stephane

Cristiano Duarte

22 years ago
On Tue, 17 Feb 2004 15:34:11 +0000, Stephane Drouard wrote: Hans, In PHP you are not allowed to explicit overload methods. Ex: class A { function x($a1) {} function x($a1, $a2) {} } The function "x" is already defined and explicit overloading is not possible, so the code above will result in parser error. In PHP you are allowed to do overriding. The code you supplied:
> class A { > function init() { ... } > function doSomething($arg1, $arg2) { ... } > } > > class B extends A { > function doSomething($arg1, $arg2, $arg3, $arg4 = null) { ... } > function doSomethingElse($arg1) { ... } > }
You are redefining the function doSomething at the child and this is "overriding". But when you use an interface, you are telling PHP that you class has a commitment to implement some functions(signatures). Since you agreed at the commitment, you can't break it doing overriding(with a different signature - arguments). The only way is overloading, but PHP doesn't support it(you can do overloading with __call, __get and __set but that's not the case). BTW, overriding with default arguments will be a nice feature. Cristiano Duarte

Hans Lellelid

22 years ago
Hi - Cristiano Duarte wrote:
> On Tue, 17 Feb 2004 15:34:11 +0000, Stephane Drouard wrote: > > Hans, > > In PHP you are not allowed to explicit overload methods. Ex: > > class A { > function x($a1) {} > function x($a1, $a2) {} > } > > The function "x" is already defined and explicit overloading is not > possible, so the code above will result in parser error. >
Yes, that I know :) Perhaps I misused the word overloading at some point. Overloading is essentially the feature that I am sorely missing here.
> In PHP you are allowed to do overriding. The code you supplied: > >>class A { >> function init() { ... } >> function doSomething($arg1, $arg2) { ... } >>} >> >>class B extends A { >> function doSomething($arg1, $arg2, $arg3, $arg4 = null) { ... } >> function doSomethingElse($arg1) { ... } >>} > > > You are redefining the function doSomething at the child and this is > "overriding". > > But when you use an interface, you are telling PHP that you class has a > commitment to implement some functions(signatures). > > Since you agreed at the commitment, you can't break it doing > overriding(with a different signature - arguments). The only way is > overloading, but PHP doesn't support it(you can do overloading with > __call, __get and __set but that's not the case).
Ok, I guess that's just how it is in PHP. The only thing I would cling to is that this behavior is inconsistent with the way classes behave: i.e. you can override a method in a class and the ($obj instanceof ParentClass) will return still return true. I think that overriding with default arguments would get around 90% of the cases where this is a problem. It won't get around my specific case, but I can live with just not using an OO hierarchy for my code. I still think that interfaces should follow the same behavior as classes (i.e. if you can override in classes you can override in interfaces), but I understand that academically this is wrong. Of course overriding is wrong period, but it's the route you have to take when overloading isn't an option. Thanks all - Hans

Cristiano Duarte

22 years ago
Hi Hans, On Tue, 17 Feb 2004 15:44:38 -0500, Hans Lellelid wrote:
> Ok, I guess that's just how it is in PHP. The only thing I would cling > to is that this behavior is inconsistent with the way classes behave: > i.e. you can override a method in a class and the ($obj instanceof > ParentClass) will return still return true.
I got your point, and you're right! PHP is inconsistent when it accepts overriding(with different signature) and still recognizes the class as "instance of" the parent. The inheritance is broken because of the lack of overloading. See this example: class A { function init() { echo "A::init"; } function doSomething($arg1, $arg2) { echo "A::doSomething"; } } class B extends A { function doSomething($arg1, $arg2, $arg3, $arg4 = null) { echo "B::doSomething"; } function doSomethingElse($arg1) { echo "B::doSomethingElse"; } } class C { static function acceptsA(A $a) { $a->doSomething(1, 2); } } $a = new A(); $b = new B(); echo ($b instanceof A) ? "YES" : "NO", "\n"; echo "trying A = ", C::acceptsA($a), "\n"; echo "trying B = ", C::acceptsA($b), "\n"; This example reports that B is an instance of A, but B doesn't implement the signature of A(because it got overriden) and then the last line generates an error. So, it's very odd, but technically B isn't an instance of A even if it inherits from A.
> I think that overriding with default arguments would get around 90% of > the cases where this is a problem. It won't get around my specific > case, but I can live with just not using an OO hierarchy for my code.
Agree. With default arguments, the inheritance isn't broken.
> I still think that interfaces should follow the same behavior as classes > (i.e. if you can override in classes you can override in interfaces), > but I understand that academically this is wrong. Of course overriding > is wrong period, but it's the route you have to take when overloading > isn't an option.
IMHO, this odd behavior exists because of the lack of overloading support. To be consistent, PHP shouldn't allow the overriding of methods with different signatures or register, at parse time, that the child break the inheritance for each parent that has methods overriden with different signatures. Best Regards, Cristiano Duarte

Hans Lellelid

22 years ago
Hi- Cristiano Duarte wrote:
> >>I still think that interfaces should follow the same behavior as classes >>(i.e. if you can override in classes you can override in interfaces), >>but I understand that academically this is wrong. Of course overriding >>is wrong period, but it's the route you have to take when overloading >>isn't an option. > > > IMHO, this odd behavior exists because of the lack of overloading support. > To be consistent, PHP shouldn't allow the overriding of methods with > different signatures or register, at parse time, that the child break the > inheritance for each parent that has methods overriden with different > signatures. >
That would also be completely acceptible, but I think it would make many people mad :) At that point PHP would need to consider implementing overloading. Obviously overloading couldn't be as fine-grained as in a typed lang, but the rule "if method has a non-compatible sig then methd is overloaded" would be nice. Maybe PHP 5.1 ? :) Hans

Marcus Börger

22 years ago
Hello Hans, Tuesday, February 17, 2004, 2:59:26 PM, you wrote:
> Andi Gutmans wrote:
>> It depends what you call different signature. If you are talking about >> default arguments, we should consider allowing those as they don't >> change the isA relationship. However, if you want us to allow >> inheriting interfaces which truly differ in function prototypes than >> that would definitely be a strong no. You can never break the isA >> relationship.
> I think at the heart of the problem (well, my problem) is the fact that > using interfaces is restricting the way that classes are allowed to > inherit from other classes.
> -- Example (1) --
> In more traditional PHP this works fine:
> class A { > function init() { ... } > function doSomething($arg1, $arg2) { ... } > }
> class B extends A { > function doSomething($arg1, $arg2, $arg3, $arg4 = null) { ... } > function doSomethingElse($arg1) { ... } > }
> $obj = new B;
> // $obj instanceof B : YES > // $obj instanceof A : YES -- but misleading
> -- Example (2) --
> Now when you throw interfaces into the mix, I won't be able to do that > anymore -- even if I use two separate interfaces for class A and class B.
> interface IA { > function init(); > function doSomething($arg1,$arg2); > }
> interface IB { > funciton init(); > function doSomething($arg1, $arg2, $arg3, $arg4 = null); > function doSomethingElse($arg1); > }
> class A implements IA { > // same class body as above > }
> class B extends A implements IB { > // same class body as above > }
> Now I get fatal errors, because class B is inheriting functionality from > A that is required by the interface IB. Actually, I think what is > happening (can someone confirm this?) is that class B is being forced to > implement both IA and IB, and because interfaces (unlike classes) are > not allowed to override methods class B cannot implement both interfaces.
No the problem is that doSomething() cannot have more than one origin.
> Posslble solutions to this (in PHP engine):
> - do not inherit interfaces: force "implements" to be explicit for > each class, so that in this case class B would not implement interface > IA. This doesn't seem like a particularly good solution, but it would > work [I think].
> - allow interfaces to override methods. I think to be consistent > with PHP class behavior this is still the best option. Just like PHP > classes can override methods, I think interfaces should be allowed. > It's just as easy to break "instanceof" using class inheritance (as I > showed in Example 1) as it would be to break it using interface > overrides. In Java this isn't an issue of course because of overloading.
> Does anyone have any other suggestions for this? I agree that radically > breaking signatures in subclasses is probably not "good practice" but in > light of the fact that PHP doesn't support method overloading, I don't > really see an alternative. My take on it is that this is just a > limitation of PHP -- and I'm fine with that. I think to be consistent > it would be good if interfaces supported the same behavior -- again, > agreeing all the while that academically it's not the "best" design.
You can still emulate that by doing your typechecking inside the function if you really need that - here again PHP choose the easy way. The way which people can easily understand. We don't want to have a second Java or C++.
-- Best regards, Marcus mailto:helly@php.net

Ferdinand Beyer

22 years ago
On 17 Feb 2004 at 8:59, Hans Lellelid wrote:
> interface IA { > function init(); > function doSomething($arg1,$arg2); > } > > interface IB { > funciton init(); > function doSomething($arg1, $arg2, $arg3, $arg4 = null); > function doSomethingElse($arg1); > } > > class A implements IA { > // same class body as above > } > > class B extends A implements IB { > // same class body as above > } > > Now I get fatal errors, because class B is inheriting functionality
from
> A that is required by the interface IB. Actually, I think what is > happening (can someone confirm this?) is that class B is being
forced to
> implement both IA and IB, and because interfaces (unlike classes)
are
> not allowed to override methods class B cannot implement both
interfaces. Well that's a tricky one. I don't think there is a reasonable solution for this problem. In my opinion, the engine should allow adding parameters with default values to the old API. In your example, we should just keep the status quo. As I said before, I don't see a solution that "fits the PHP goal". Such situations are certainly very rare and I can live that restriction.
-- Ferdinand Beyer <fb@fbeyer.com>