Class Posing

php.internals

Sebastian Bergmann

18 years ago
From [1]: Objective-C permits a class to wholly replace another class within a program. The replacing class is said to "pose as" the target class. All messages sent to the target class are then instead received by the posing class. There are several restrictions on which classes can pose: * A class may only pose as one of its direct or indirect superclasses. [The other restrictions do not apply to PHP] Earlier this year, Johannes implemented class posing for PHP as follows: <?php class Foo {} class Bar extends Foo {} function new_overload($className) { if ($className == 'Foo') { return new Bar; } // ... } $o = new Foo; // $o is an object of Foo. register_new_overload('new_overload'); $o = new Foo; // $o is an object of Bar. ?> We (Johannes, Marcus, Sara, and myself) then discussed where to put this functionality. Outside of core, there were two places that both make sense: pecl/operator and pecl/runkit. However, to make this a viable mechanism that can be used in tools such as PHPUnit (for which I could really use this functionality), we agreed that it actually belongs into the core. Opinions? Needless to say that I would love to see this in PHP 5.3 ;-)
-- [1] http://en.wikipedia.org/wiki/Objective_C#Posing -- Sebastian Bergmann http://sebastian-bergmann.de/ GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69

David Zülke

18 years ago
would it be possible to overload final classes? - David Am 02.10.2007 um 11:32 schrieb Sebastian Bergmann:

Richard Quadling

18 years ago
On 02/10/2007, David Zülke <dz@bitxtender.com> wrote: > would it be possible to overload final classes? > > - David > > > Am 02.10.2007 um 11:32 schrieb Sebastian Bergmann: > > > From [1]: > > > > Objective-C permits a class to wholly replace another class > > within a > > program. The replacing class is said to "pose as" the target class. > > All messages sent to the target class are then instead received by > > the posing class. > > > > There are several restrictions on which classes can pose: > > > > * A class may only pose as one of its direct or indirect > > superclasses. > > > > [The other restrictions do not apply to PHP] > > > > Earlier this year, Johannes implemented class posing for PHP as > > follows: > > > > <?php > > class Foo {} > > class Bar extends Foo {} > > > > function new_overload($className) > > { > > if ($className == 'Foo') { > > return new Bar; > > } > > > > // ... > > } > > > > $o = new Foo; > > // $o is an object of Foo. > > > > register_new_overload('new_overload'); > > > > $o = new Foo; > > // $o is an object of Bar. > > ?> > > > > We (Johannes, Marcus, Sara, and myself) then discussed where to > > put this > > functionality. Outside of core, there were two places that both make > > sense: pecl/operator and pecl/runkit. > > > > However, to make this a viable mechanism that can be used in tools > > such > > as PHPUnit (for which I could really use this functionality), we > > agreed > > that it actually belongs into the core. > > > > Opinions? Needless to say that I would love to see this in PHP > > 5.3 ;-) > > > > -- > > [1] http://en.wikipedia.org/wiki/Objective_C#Posing You're code looks like a factory. Is that your intention? Maybe __factory would be a more obvious name? -- ----- Richard Quadling Zend Certified Engineer : http://zend.com/zce.php?c=ZEND002498&r=213474731 "Standing on the shoulders of some very clever giants!"

Sebastian Bergmann

18 years ago
Richard Quadling schrieb:
> You're code looks like a factory. Is that your intention? Maybe > __factory would be a more obvious name?
The application of this mechanism is not limited to implementing factories and singletons.
-- Sebastian Bergmann http://sebastian-bergmann.de/ GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69

Sebastian Bergmann

18 years ago
David Zülke schrieb:
> would it be possible to overload final classes?
Of course. We are not changing the class declaration (compile-time) but intercept the object creation (run-time).
-- Sebastian Bergmann http://sebastian-bergmann.de/ GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69

Sebastian Bergmann

18 years ago
Sebastian Bergmann schrieb:
>> would it be possible to overload final classes? > Of course. We are not changing the class declaration (compile-time) but > intercept the object creation (run-time).
I may have been unclear here: a class may pose for a class that contains final methods. The posing class, by way of the restriction imposed on posing classes, can of course not override final methods of its parent class.
-- Sebastian Bergmann http://sebastian-bergmann.de/ GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69

Guilherme Blanco

18 years ago
Hi Sebastian and PHP list, That's a very interesting idea and I'd use it too. Yesterday night I was developing a set of classes that it could apply this idea too. Altho I found this idea really interesting, I have a suggestion for it. Instead of use a function to handle new overloads, I suggest a magic method, something like __new. The idea behind this: <?php class Foo { function __construct($type = 'Foo') { echo 'Created ', $type; } function __new( $type ) { if ($type == 'Bar') { return new Bar(); } } } class Bar { function __construct() { echo 'Bar here!'; } } $bar = new Foo('Bar'); // Creates Bar instance ?> Dunno the impact of this in the source, but my humble idea is that it independs of inheritance. Of course this would be more acceptable for super and extended classes, and here is a true usage of it: class Validator { private $_country; function __construct() {} function __new($country) { switch ($country) { case 'Brazil': return new Validator_Brazil(); default: return new Validator_US(); } } } interface IValidator_Country { function validate($type, $value); } class Validator_Brazil implements IValidator_Country { function __construct() { ... } function validate($type, $value) { ... } } class Validator_US implements IValidator_Country { function __construct() { ... } function validate($type, $value) { ... } } $o = new Validator('Brazil'); // Creates Validator_Brazil echo ($o->validate('zip', '13561000')) ? 'Zip code is valid' : 'Zip is not valid'; The basic idea is to check for __new existance and call it instead of __construct. If there's no return or null after __new call, then call the __contruct as currently it does. So, what do you think??? =) Regards, On 10/2/07, Sebastian Bergmann <sb@sebastian-bergmann.de> wrote: > From [1]: > > Objective-C permits a class to wholly replace another class within a > program. The replacing class is said to "pose as" the target class. > All messages sent to the target class are then instead received by > the posing class. > > There are several restrictions on which classes can pose: > > * A class may only pose as one of its direct or indirect > superclasses. > > [The other restrictions do not apply to PHP] > > Earlier this year, Johannes implemented class posing for PHP as follows: > > <?php > class Foo {} > class Bar extends Foo {} > > function new_overload($className) > { > if ($className == 'Foo') { > return new Bar; > } > > // ... > } > > $o = new Foo; > // $o is an object of Foo. > > register_new_overload('new_overload'); > > $o = new Foo; > // $o is an object of Bar. > ?> > > We (Johannes, Marcus, Sara, and myself) then discussed where to put this > functionality. Outside of core, there were two places that both make > sense: pecl/operator and pecl/runkit. > > However, to make this a viable mechanism that can be used in tools such > as PHPUnit (for which I could really use this functionality), we agreed > that it actually belongs into the core. > > Opinions? Needless to say that I would love to see this in PHP 5.3 ;-) > > -- > [1] http://en.wikipedia.org/wiki/Objective_C#Posing > > -- > Sebastian Bergmann http://sebastian-bergmann.de/ > GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69 > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php > > -- Guilherme Blanco - Web Developer CBC - Certified Bindows Consultant Cell Phone: +55 (16) 9166-6902 MSN: guilhermeblanco@hotmail.com URL: http://blog.bisna.com São Carlos - SP/Brazil

Sebastian Bergmann

18 years ago
Guilherme Blanco schrieb:
> Altho I found this idea really interesting, I have a suggestion for > it. Instead of use a function to handle new overloads, I suggest a > magic method, something like __new.
__new() was what I initially proposed to Johannes but IIRC there were some performance implications with that. Johannes?
-- Sebastian Bergmann http://sebastian-bergmann.de/ GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69

Johannes Schlueter

18 years ago
Hi, On Tue, 2007-10-02 at 15:54 +0200, Sebastian Bergmann wrote:
> Guilherme Blanco schrieb: > > Altho I found this idea really interesting, I have a suggestion for > > it. Instead of use a function to handle new overloads, I suggest a > > magic method, something like __new. > > __new() was what I initially proposed to Johannes but IIRC there were > some performance implications with that. Johannes?
The initial implementation Sebastian refers to is a conference hack I did, which was done to see whether it's possible and give Sebastian a thing to test the feature - there wasn't much thinking involved :-) For an extension, which the current implementation is, using the register function is the best approach I see. Else we would have to to the look-up for the __new() function on every object instantiation. The existence of that function can't be cached since it could be defined any time. (While for an extension, used for testing, performance is not #1 prio) If such an feature would become an engine feature one could cache the declaration of __new() during the function declaration - which in that case would, indeed, be the best. But all that are implementation details. The question is: Do we need such a feature inside Core PHP or is an extension overloading ZEND_NEW enough? The only need I see is mocking objects for a unit test. This is usually done on a test system so installing such an extension shouldn't be to hard (especially for somebody who is able to do unit tests using mock objects...) One might also think about taking XDebug's code coverage stuff and the ZEND_NEW overloading and other stuff to build a phpunit extension - but that's no topic for internals :-) johannes

Stanislav Malyshev

18 years ago
> For an extension, which the current implementation is, using the > register function is the best approach I see. Else we would have to to
I would prefer some special function to new handler. This feature has very narrow use (I still not entirely convinced it even belongs to core - it's too kludgy and can be easily worked around by using correct patterns).
> But all that are implementation details. The question is: Do we need > such a feature inside Core PHP or is an extension overloading ZEND_NEW > enough?
I'd say it's better to overload class' create_object and not an opcode. And I personally don't feel it's something that should be widely used, so Core may not be the best place. Maybe having some test extension (disabled by default, enabled on test machines) would be a better way?
-- Stanislav Malyshev, Zend Software Architect stas@zend.com http://www.zend.com/ (408)253-8829 MSN: stas@zend.com

Greg Beaver

18 years ago
Johannes Schlüter wrote:
> The only need I see is mocking objects for a unit test. This is usually > done on a test system so installing such an extension shouldn't be to > hard (especially for somebody who is able to do unit tests using mock > objects...)
This is unnecessary in a well-designed project. Rather than directly instantiating, encapsulate the object creation in a method, then override the method that does the creation in a mock object. No code mods and certainly no engine mods are needed to test properly. The feature is neat, but not a good candidate for core as proposed imo. Greg

Jared Williams

18 years ago
Ideally think I'd prefer it finer grained... interface A { } class AImpl implements A { } register('A', function() { return new AImpl(); }); register('Foo', function() { return new Bar(); }); $foo = new Foo(); $a = new A(); Would also require functions being values... Which would also be nice. J

Stanislav Malyshev

18 years ago
> Earlier this year, Johannes implemented class posing for PHP as follows: > > <?php > class Foo {} > class Bar extends Foo {} > > function new_overload($className) > { > if ($className == 'Foo') { > return new Bar; > } > > // ... > } > > $o = new Foo; > // $o is an object of Foo. > > register_new_overload('new_overload'); > > $o = new Foo; > // $o is an object of Bar. > ?>
This looks like factory pattern. What's wrong with implementing it as factory?
> However, to make this a viable mechanism that can be used in tools such > as PHPUnit (for which I could really use this functionality), we agreed > that it actually belongs into the core.
I personally don't see much of the use of it in the core, and it definitely adds very "magic" things - you don't know anymore what class you are instantiating when you run "new Foo". My opinion is it belongs to places like "runkit" which do tricks with the language, but I don't see much use for it in the core. Could you explain more why existing pattern won't work for the same?
-- Stanislav Malyshev, Zend Software Architect stas@zend.com http://www.zend.com/ (408)253-8829 MSN: stas@zend.com

Sebastian Bergmann

18 years ago
Stanislav Malyshev schrieb:
> This looks like factory pattern. What's wrong with implementing it as > factory?
Like I said before, the factory and singleton patterns are just example applications of this mechanism. What I want to use them for in PHPUnit is improving the Mock Objects system. Let us consider the following code: public function foo() { $bar = new Bar; $returh = $bar->doSomething(); // do something with $return return 'some value'; } When I want to test the foo() method, I want to stub out the usage of the Bar class, for example, and have Bar::doSomething() return a pre-configured value instead of performing its normal operation. As I cannot pass a stubbed version of $bar into the method, I currently do not have a way to stub Bar. With class posing, however, I can override the "new Bar" and have it create an object of my stub implementation of Bar instead. I hope this makes my motivation for the feature more clear.
-- Sebastian Bergmann http://sebastian-bergmann.de/ GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69

Stanislav Malyshev

18 years ago
> Like I said before, the factory and singleton patterns are just example > applications of this mechanism.
Right, but I'd like to see why we *need* it - i.e. what it allows to do that other things don't. I'm afraid that we might have a case of featuritis here (not meaning this specific proposal, I'm talking more in general state of affairs) in 5.3 - we will turn PHP from simple language into minefield of magic methods where you can't possibly know what the code you are looking at actually does. It might be useful for some applications but might hurt the language as a whole. It also can have a lot of implications what have unintended - i.e. how typehints are supposed to work with this? How reflection and autoloading would work? What with static methods and variables?
> What I want to use them for in PHPUnit is improving the Mock Objects > system.
As far as I know, Java has unit tests but doesn't allow replacing classes. How does it work in Java?
> I hope this makes my motivation for the feature more clear.
Yes, I can see the motivation now, but I'm not entirely convinced allowing to replace classes with other classes would be the best solution. I know there are OO languages extensively using unit testing and not having this feature - I'd propose looking into how it is done there.
-- Stanislav Malyshev, Zend Software Architect stas@zend.com http://www.zend.com/ (408)253-8829 MSN: stas@zend.com

Sebastian Bergmann

18 years ago
Stanislav Malyshev schrieb:
> As far as I know, Java has unit tests but doesn't allow replacing > classes. How does it work in Java?
Stubs / Mock Objects !== Unit Tests. Class Posing allows for better stubs / mock objects which in turn are tools to write better unit tests.
-- Sebastian Bergmann http://sebastian-bergmann.de/ GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69

Stanislav Malyshev

18 years ago
> Stubs / Mock Objects !== Unit Tests. Class Posing allows for better > stubs / mock objects which in turn are tools to write better unit > tests.
OK, I see that. The question is - does any of the known unit test systems use mock objects? Does it do that in the language that doesn't allow "new Foo" to create object of entirely unrelated class? If so - how it is usually done?
-- Stanislav Malyshev, Zend Software Architect stas@zend.com http://www.zend.com/ (408)253-8829 MSN: stas@zend.com

Jared Williams

18 years ago
I think they end up using the dependency injection pattern. http://www.picocontainer.org/ java implementation with a PHP port. http://sourceforge.net/projects/phemto/ is a more lightweight PHP implementation. Its README http://phemto.cvs.sourceforge.net/phemto/phemto/README?revision=1.1.1.1&view =markup . Problem is the cost of setting up these, by registering implementations, performing reflection and so on is expensive especially considering the stateless nature of PHP. J

Stanislav Malyshev

18 years ago
> Problem is the cost of setting up these, by registering implementations, > performing reflection and so on is expensive especially considering the > stateless nature of PHP.
Thanks, will look at these. I'm extremely uncomfortable with engine change that would allow "new Foo" produce an object that is not Foo.
-- Stanislav Malyshev, Zend Software Architect stas@zend.com http://www.zend.com/ (408)253-8829 MSN: stas@zend.com

Sebastian Bergmann

18 years ago
Stanislav Malyshev schrieb:
> Thanks, will look at these. I'm extremely uncomfortable with engine > change that would allow "new Foo" produce an object that is not Foo.
The restriction "A class may only pose as one of its direct or indirect superclasses." I mentioned ensures that "new Foo" can only produce objects that are in an is_a relationship with Foo.
-- Sebastian Bergmann http://sebastian-bergmann.de/ GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69

Arne Blankerts

18 years ago
On Wed, 2007-10-03 at 07:24 +0200, Sebastian Bergmann wrote:
> Stanislav Malyshev schrieb: > > Thanks, will look at these. I'm extremely uncomfortable with engine > > change that would allow "new Foo" produce an object that is not Foo. > > The restriction > > "A class may only pose as one of its direct or indirect superclasses." > > I mentioned ensures that "new Foo" can only produce objects that are in > an is_a relationship with Foo.
That will conflict with your previous statement, that one can "override" final classes. And it will cause serious trouble with anything marked private in the original class. Or won't it? CYA Arne
-- Those who do not understand Unix are condemned to reinvent it, poorly (Henry Spencer)

Marcus Börger

18 years ago
Hello Stanislav, new Foo will still return a Foo instance! marcus Wednesday, October 3, 2007, 12:00:12 AM, you wrote:
>> Problem is the cost of setting up these, by registering implementations, >> performing reflection and so on is expensive especially considering the >> stateless nature of PHP.
> Thanks, will look at these. I'm extremely uncomfortable with engine > change that would allow "new Foo" produce an object that is not Foo. > -- > Stanislav Malyshev, Zend Software Architect > stas@zend.com http://www.zend.com/ > (408)253-8829 MSN: stas@zend.com
Best regards, Marcus

Sebastian Bergmann

18 years ago
Jared Williams schrieb:
> I think they end up using the dependency injection pattern.
Of course you can argue that the code was badly {designed|written} in the example I showed and should be refactored before testing. More often than not, though, you cannot do this (3rd party code, for example) and then you cannot use mock objects without class posing.
-- Sebastian Bergmann http://sebastian-bergmann.de/ GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69

Sebastian Bergmann

18 years ago
Stanislav Malyshev schrieb:
> lot of implications what have unintended - i.e. how typehints are > supposed to work with this? How reflection and autoloading would work?
Typehints continue working because of the restriction that applies to this mechanism (see other mail(s)). Reflection{Class|Object}($object) does not care how $object has been created. __autoload() will be trigger just as before by the new operators inside the new-handler function (where they are, of course, not overloaded).
-- Sebastian Bergmann http://sebastian-bergmann.de/ GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69

Timm Friebe

18 years ago
Hi,
> public function foo() > { > $bar = new Bar;
[...]
> } > > When I want to test the foo() method, I want to stub out the usage of > the Bar class, for example, and have Bar::doSomething() return a > pre-configured value instead of performing its normal operation.
This is where you'd start refactoring the above sourcecode. I don't like this idea at all, because it really only helps you with testing legacy source you might not be able to or do not want to change (want to say: rare use-case IMO). If you really need to intercept construction, you can still weave your interception functionality into sourcecode as you'd be doing in any AOP library. Simply write a stream wrapper for the file:// stream or add a filter to it to intercept all includes and / or requires your legacy code might contain and rewrite the sourcecode while loading, replacing "new Foo" by "newinstance('Foo')" - this might even be possible in a safe manner by using preg_replace. - Timm