php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #71248 Wrong interface is enforced
Submitted: 2015-12-30 17:46 UTC Modified: 2016-01-12 13:33 UTC
From: norbert at linuxnetworks dot de Assigned: dmitry
Status: Closed Package: Class/Object related
PHP Version: 7.0.1 OS: Ubuntu 14.04
Private report: No CVE-ID:
 [2015-12-30 17:46 UTC] norbert at linuxnetworks dot de
Description:
------------
I ran into a weired problem with interfaces in combination with namespaces. In the Aimeos library, decorators can be wrapped around provider objects. The provider objects use this constructor signature:

public function __construct( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MShop\Service\Item\Iface $serviceItem );

Decorators use this one:

public function __construct( \Aimeos\MShop\Service\Provider\Iface $provider,
\Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MShop\Service\Item\Iface $serviceItem );

Both have their own interfaces (Provider/Factory/Iface.php and Provider/Decorator/Iface.php) and the Factory interface isn't used for decorators and vice versa. Despite that fact, PHP7 complains that the decorators doesn't implement the Factory interface.

I've stripped the source code down as much as possible (classes and interfaces are mostly empty) and pushed it to a GitHub repository:
https://github.com/nos3/php7

Furthermore, on travis-ci a test against 5.6 and 7.0 have been done:
https://travis-ci.org/nos3/php7

Expected result:
----------------
No error like for PHP 5.6

Actual result:
--------------
PHP Fatal error:  Declaration of Aimeos\MShop\Service\Provider\Decorator\Base::__construct(Aimeos\MShop\Service\Provider\Iface $provider, Aimeos\MShop\Context\Item\Iface $context, Aimeos\MShop\Service\Item\Iface $serviceItem) must be compatible with Aimeos\MShop\Service\Provider\Factory\Iface::__construct(Aimeos\MShop\Context\Item\Iface $context, Aimeos\MShop\Service\Item\Iface $serviceItem) in /home/vagrant/Code/Laravel/php7/Provider/Decorator/Base.php on line 20

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-01-03 13:12 UTC] cmb@php.net
Possibly a duplicate of bug #69629.
 [2016-01-03 14:00 UTC] nikic@php.net
Here's a self-contained and somewhat reduced reproduce script. It seems to be important that the last class is defined in a separate file:

<?php

namespace Provider;

interface Iface {}

abstract class Base {
    public function __construct(
        \Aimeos\MShop\Context\Item\Iface $context, \Item\Iface $serviceItem
    ) { }
}

namespace Provider\Factory;

interface Iface {
    public function __construct(
        \Aimeos\MShop\Context\Item\Iface $context, \Item\Iface $serviceItem);
}

namespace Provider\Delivery;

interface Iface extends
    \Provider\Iface, \Provider\Factory\Iface { }

abstract class Base extends \Provider\Base { }

class Manual
	extends \Provider\Delivery\Base
	implements \Provider\Delivery\Iface
{ }

namespace Provider\Decorator;

interface Iface
	extends \Provider\Iface
{
    public function __construct(\Provider\Iface $provider, $context, \Item\Iface $serviceItem);
}

$code = <<<'PHP'
namespace Provider\Decorator;

abstract class Base
	extends \Provider\Base
{
	public function __construct(\Provider\Iface $provider, $context, \Item\Iface $serviceItem) { }
}
PHP;
eval($code);
 [2016-01-03 14:05 UTC] nikic@php.net
Ooops, I pasted the non-reduced code. Here's the correct version:

<?php

namespace Provider;

interface Iface { }

abstract class Base {
    public function __construct(\Item\Iface $serviceItem) { }
}

namespace Provider\Factory;

interface Iface {
    public function __construct(\Item\Iface $serviceItem);
}

namespace Provider\Delivery;

interface Iface extends \Provider\Iface, \Provider\Factory\Iface { }

abstract class Base extends \Provider\Base { }

class Manual extends \Provider\Delivery\Base implements \Provider\Delivery\Iface { }

$code = <<<'PHP'
namespace Provider\Decorator;

abstract class Base extends \Provider\Base {
    public function __construct(\Provider\Iface $provider, \Item\Iface $serviceItem) { }
}
PHP;
eval($code);
 [2016-01-05 15:26 UTC] nikic@php.net
The problem is that in PHP 7 we will share the op_array between inherited class methods if it does not use static variables, under the assumption that all their members will stay the same.

However this assumption is not true in this case, namely function->common.prototype may differ. In this case Provider\Base::__construct() has no prototype, while Provider\Delivery\Manual::__construct() [inherited from Provider\Base::__construct()] has Provider\Factory\Iface::__construct() as the prototype. As the op_array is shared between both this inheritance will clobber common.prototype for Provider\Base::__construct() as well. This then causes the inheritance error in Provider\Decorator\Base.

We could fix it by simply always using a separate op_array (i.e. drop http://lxr.php.net/xref/PHP_MASTER/Zend/zend_inheritance.c#77). Is there a better way to somehow only copy if we need a different prototype?
 [2016-01-11 21:42 UTC] nikic@php.net
-Assigned To: +Assigned To: dmitry
 [2016-01-11 21:42 UTC] nikic@php.net
@dmitry: Regarding the last comment, do you see a better way to solve it, short of always copying the op_array struct during inheritance?
 [2016-01-12 01:10 UTC] dmitry@php.net
We should try to lazely duplicate op_array, only in case of op_array.prototype modification (copy on write).
 [2016-01-12 13:33 UTC] dmitry@php.net
This patch should fix the problem

https://gist.github.com/dstogov/81ded758484a608b831b

@nikic: please review.
 [2016-01-13 08:43 UTC] dmitry@php.net
Automatic comment on behalf of dmitry@zend.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=50be2c89bed61a1928cd1dd0c226fe62769344dd
Log: Fixed bug #71248 (Wrong interface is enforced)
 [2016-01-13 08:43 UTC] dmitry@php.net
-Status: Assigned +Status: Closed
 [2016-07-20 11:34 UTC] davey@php.net
Automatic comment on behalf of dmitry@zend.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=50be2c89bed61a1928cd1dd0c226fe62769344dd
Log: Fixed bug #71248 (Wrong interface is enforced)
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Fri Jul 21 08:01:41 2017 UTC