I've just been looking back at the history of this previous conversation...
http://marc.info/?l=php-internals&m=132673741606531&w=2
as a mockist tester I'd really REALLY like to see this be possible but I
can see the problem with the original patch modifying the actual existing
class.
One solution I propose is rather than modify it would it be possible for
the reflection class to be able to duplicate the class with the finals
removed so something like this could be possible:
final class A
{
}
$r = new ReflectionClass('A');
$r->defineExtendableClass('ExtendableA');
class MockA extends ExtendabledA
{
}
I'm unfamiliar with the PHP codebase so I thought I'd just ask here to find
out if it sounds possible before diving into the code
Thoughts?
Regards,
Tom
Hi Tom
I've just been looking back at the history of this previous conversation...
http://marc.info/?l=php-internals&m=132673741606531&w=2
as a mockist tester I'd really REALLY like to see this be possible but I
can see the problem with the original patch modifying the actual existing
class.One solution I propose is rather than modify it would it be possible for
the reflection class to be able to duplicate the class with the finals
removed so something like this could be possible:final class A
{
}$r = new ReflectionClass('A');
$r->defineExtendableClass('ExtendableA');
class MockA extends ExtendabledA
{
}I'm unfamiliar with the PHP codebase so I thought I'd just ask here to find
out if it sounds possible before diving into the code
Yes it's definitely possible, all you need to do is duplicate the
class definition and perform &= ~ZEND_ACC_FINAL on the class entry
structure flags, however...
Joe Watkins' uopz [1][2] extension already provides a mechanism to do
what you want to do. I've not tested this, but I quickly bounced a
couple of questions off Joe and I believe this should work:
final class A {}
class MockA {}
uopz_extend('MockA', 'A');
A word of warning though: if a method is explicitly declared final
(rather than implicitly because the class is final), and the mock
overrides this method, this will be checked at runtime and cause a
fatal error. In order to work around this, you need to use
uopz_flags() as well:
class B {
final public function c() {}
}
class MockB {
public function c() {}
}
uopz_flags('B', 'c', ZEND_ACC_PUBLIC); // remove ZEND_ACC_FINAL
uopz_flags('B', 'c', );
Note also that if you are using this approach - modifying symbol flags
at runtime - it is important that you do not declare the inheritance
relationship explicitly (i.e. class MockA extends A {}) as this will
error out before the runtime modifications are made.
If you want to look at adding similar functionality to reflection,
then looking at how uopz works would be a good place to start - but
personally I'm not sure if there's any value in this since anyone who
wants the functionality can just use uopz.
[1] https://github.com/krakjoe/uopz
[2] http://php.net/uopz
Thoughts?
Regards,
Tom
Thanks, Chris
Hi Tom
I've just been looking back at the history of this previous conversation...
http://marc.info/?l=php-internals&m=132673741606531&w=2
as a mockist tester I'd really REALLY like to see this be possible but I
can see the problem with the original patch modifying the actual existing
class.One solution I propose is rather than modify it would it be possible for
the reflection class to be able to duplicate the class with the finals
removed so something like this could be possible:final class A
{
}$r = new ReflectionClass('A');
$r->defineExtendableClass('ExtendableA');
class MockA extends ExtendabledA
{
}I'm unfamiliar with the PHP codebase so I thought I'd just ask here to find
out if it sounds possible before diving into the codeYes it's definitely possible, all you need to do is duplicate the
class definition and perform &= ~ZEND_ACC_FINAL on the class entry
structure flags, however...Joe Watkins' uopz [1][2] extension already provides a mechanism to do
what you want to do. I've not tested this, but I quickly bounced a
couple of questions off Joe and I believe this should work:final class A {}
class MockA {}
uopz_extend('MockA', 'A');A word of warning though: if a method is explicitly declared final
(rather than implicitly because the class is final), and the mock
overrides this method, this will be checked at runtime and cause a
fatal error. In order to work around this, you need to use
uopz_flags() as well:class B {
final public function c() {}
}
class MockB {
public function c() {}
}
uopz_flags('B', 'c', ZEND_ACC_PUBLIC); // remove ZEND_ACC_FINAL
uopz_flags('B', 'c', );
That last line should read:
uopz_extend('MockB', 'B');
Note also that if you are using this approach - modifying symbol flags
at runtime - it is important that you do not declare the inheritance
relationship explicitly (i.e. class MockA extends A {}) as this will
error out before the runtime modifications are made.If you want to look at adding similar functionality to reflection,
then looking at how uopz works would be a good place to start - but
personally I'm not sure if there's any value in this since anyone who
wants the functionality can just use uopz.[1] https://github.com/krakjoe/uopz
[2] http://php.net/uopzThoughts?
Regards,
TomThanks, Chris
Hi Tom,
Thoughts?
Possible? Probably, yes.
I'd actually argue that it may not be a good solution: it seems to me that
if you have to resort to this kind of mocking, then someone type-hinted
against a concrete implementation, which is a smell. Shouldn't that be
fixed instead?
What particular use-case denies using an interface or abstract type instead?
Marco Pivetta
I've just been looking back at the history of this previous conversation...
http://marc.info/?l=php-internals&m=132673741606531&w=2
as a mockist tester I'd really REALLY like to see this be possible but I
can see the problem with the original patch modifying the actual existing
class.One solution I propose is rather than modify it would it be possible for
the reflection class to be able to duplicate the class with the finals
removed so something like this could be possible:final class A
{
}$r = new ReflectionClass('A');
$r->defineExtendableClass('ExtendableA');
class MockA extends ExtendabledA
{
}I'm unfamiliar with the PHP codebase so I thought I'd just ask here to find
out if it sounds possible before diving into the codeThoughts?
Regards,
Tom
apart from the thread you linked, we also touched this topic in the
following pull request:
https://github.com/php/php-src/pull/733
--
Ferenc Kovács
@Tyr43l - http://tyrael.hu
I've just been looking back at the history of this previous conversation...
http://marc.info/?l=php-internals&m=132673741606531&w=2
as a mockist tester I'd really REALLY like to see this be possible but I
can see the problem with the original patch modifying the actual existing
class.One solution I propose is rather than modify it would it be possible for
the reflection class to be able to duplicate the class with the finals
removed so something like this could be possible:final class A
{
}$r = new ReflectionClass('A');
$r->defineExtendableClass('ExtendableA');
class MockA extends ExtendabledA
{
}I'm unfamiliar with the PHP codebase so I thought I'd just ask here to find
out if it sounds possible before diving into the codeThoughts?
Regards,
Tom
Supporting this at an engine level would be harder than you think.
We already had lots of difficulties patching
newInstanceWithoutConstructor() to support internal classes (5.6)
because they've never been designed to support creation without the
call to their constructor.
Removing (or giving a way to remove) FINAL should be OK for userland
classes, but that will be hard to support for internal classes and
would require lots of work. Some of them could even never support it.
Julien.Pauli
I've just been looking back at the history of this previous conversation...
http://marc.info/?l=php-internals&m=132673741606531&w=2
as a mockist tester I'd really REALLY like to see this be possible but I
can see the problem with the original patch modifying the actual existing
class.One solution I propose is rather than modify it would it be possible for
the reflection class to be able to duplicate the class with the finals
removed so something like this could be possible:final class A
{
}$r = new ReflectionClass('A');
$r->defineExtendableClass('ExtendableA');
class MockA extends ExtendabledA
{
}I'm unfamiliar with the PHP codebase so I thought I'd just ask here to find
out if it sounds possible before diving into the codeThoughts?
Regards,
TomSupporting this at an engine level would be harder than you think.
We already had lots of difficulties patching
newInstanceWithoutConstructor() to support internal classes (5.6)
because they've never been designed to support creation without the
call to their constructor.Removing (or giving a way to remove) FINAL should be OK for userland
classes, but that will be hard to support for internal classes and
would require lots of work. Some of them could even never support it.
I don't think that it even makes sense to have this as a possibility
for internal classes. tbh I doubt it makes sense to do it in userland
either (class shouldn't have been declared final in the first place)
but there may some testing applications that are not immediately
apparent to me, as implied by the code sample in the original mail.
If such a feature were to be implemented as part of reflection as
suggested (I'm not a fan), not supporting internal classes would be
totally acceptable IMO.
I don't think that it even makes sense to have this as a possibility
for internal classes.
Even leaving everything else outside, I think internal classes should
become less magic and more transparent over time.
The current "fixing" of internal classes has been to disallow operations in
other PHP APIs, while the correct fix would be to make them final
, with a
final public function __wakeup
, with a final public function __construct
and so on: at least the limitations would make sense from a
userland perspective as well.
Marco Pivetta
Hi!
I've just been looking back at the history of this previous conversation...
http://marc.info/?l=php-internals&m=132673741606531&w=2
as a mockist tester I'd really REALLY like to see this be possible but I
can see the problem with the original patch modifying the actual existing
class.One solution I propose is rather than modify it would it be possible for
the reflection class to be able to duplicate the class with the finals
removed so something like this could be possible:
I don't think it is a good idea. If class is marked as final, there must
be a reason why it can not be extended, and if you break that, then bad
things can happen - such as we seen in the example of abusing
unserializer for internal classes. For internal classes, it may segfault
or worse, for user classes, there can be weird undefined behavior. I
don't think it is a good idea to allow such things in core. If the class
really needs to be extended, just make it not final.
--
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
Hi!
I've just been looking back at the history of this previous conversation...
http://marc.info/?l=php-internals&m=132673741606531&w=2
as a mockist tester I'd really REALLY like to see this be possible but I
can see the problem with the original patch modifying the actual existing
class.One solution I propose is rather than modify it would it be possible for
the reflection class to be able to duplicate the class with the finals
removed so something like this could be possible:I don't think it is a good idea. If class is marked as final, there must
be a reason why it can not be extended, and if you break that, then bad
things can happen - such as we seen in the example of abusing
unserializer for internal classes. For internal classes, it may segfault
or worse, for user classes, there can be weird undefined behavior. I
don't think it is a good idea to allow such things in core. If the class
really needs to be extended, just make it not final.
I would argue the same, seems logical to me.
Julien.Pauli