php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #48376 Method scope demotion allowed from private scope grandparent
Submitted: 2009-05-24 17:14 UTC Modified: 2009-05-24 22:21 UTC
From: kev dot simpson at gmail dot com Assigned:
Status: Not a bug Package: Class/Object related
PHP Version: 5.2.9 OS: Windows XP Home
Private report: No CVE-ID: None
 [2009-05-24 17:14 UTC] kev dot simpson at gmail dot com
Description:
------------
Tested in 5.2.5, 5.2.9-2 and 6.0.0-dev using Apache 5.0.59

When a class uses a private scope on a method, any grandchild class can demote the scope regardless of what the primary class's immediate child scope is.  Curious if I can exploit this as a feature or if it is indeed a bug.
Example is in order to help visualize.

Reproduce code:
---------------
<?php

class TestParent
{
    private function __construct()
    {
    }
}

class Child extends TestParent
{
    public function __construct() // Ok, parent is private and unviewable, so not technically overridding
    {
        printf("%s\n", __CLASS__);
    }
}

class GrandChild extends Child
{
    protected function __construct() // Override here; scope reduction is prohibited in PHP
    {
        parent::__construct();
        printf("%s\n", __CLASS__);
    }

    public static function createInstance()
    {
        return new self;
    }
}

GrandChild::createInstance();

?>

Expected result:
----------------
I expect this to produce a fatal error indicating that 'Access level to GrandChild::__construct() must be public (as in Child) in ....'

Actual result:
--------------
Child
GrandChild

Returning of object of instance GrandChild.

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2009-05-24 17:58 UTC] johannes@php.net
Thank you for taking the time to write to us, but this is not
a bug. Please double-check the documentation available at
http://www.php.net/manual/ and the instructions on how to report
a bug at http://bugs.php.net/how-to-report.php

We decided to do less strict checking on the constructor. So this is the way it is meant to be.
 [2009-05-24 22:21 UTC] kev dot simpson at gmail dot com
I'm not quite certain I follow you on this.  As of 5.2.9-2 this behaviour only exists during a Grandfather > Father > Son relationship not during a standard parent > child relationship.  This also covers any method including interface definitions and methods, not just constructors.  Through my experience I have not been able to reduce the scope of any method in a child from a parent.
The problem I see is that
1. A child of a single superclass cannot reduce any public functions; however, adding a grandparent superclass to the father with private scoping allows grandchildren to override their parent's scope.

2. Interfaces will be destroyed.  If the 'Father' or first child class is implementing the interface while the GrandParent class defines the same as private allowing a grandchild to decay its scope to private.  This always results in an uncatchable fatal error when the method is called, even though the 'son' is considered an instanceof the given interface.

I don't think that an interfaced class (or any class extending an interfaced class) should have a private / protected declaration of the contracted methods by the interface.
Don't get me wrong, I love the idea of being able to reduce the scope of a constructor in an extending class in order to develop singleton and similar objects.  However, the inability to try/catch a fatal error on an interfaced method is IMO a bug.  Here is a little more code to show what I mean:

<?php

ini_set('display_errors', 1);
error_reporting(E_ALL);

interface IInterface
{
	public function test();
}

class Grandfather
{
	private function __construct()
	{
	}

	private function test()
	{
		printf("%s\n", __METHOD__);
	}
}

class Father extends Grandfather implements IInterface
{
	public function __construct()
	{
		printf("%s\n", __METHOD__);
	}

	public function test()
	{
		printf("%s\n", __METHOD__);
	}
}

class Son extends Father
{
	protected function __construct()
	{
		parent::__construct();
		printf("%s\n", __METHOD__);
	}

	public static function createInstance()
	{
		return new self;
	}

	protected function test()
	{
		parent::test();
		printf("%s\n", __METHOD__);
	}
}


class CorrectScope implements IInterface
{
	public function __construct()
	{
		printf("%s\n", __METHOD__);
	}
	
	public function test()
	{
		printf("%s\n", __METHOD__);
	}
}

$t = Son::createInstance();
var_dump($t);
if ($t instanceof IInterface)
{
	print("\$t is an IInterface, check test:\n");
	// Fatal error on an interface call
	printf("Results of \$t->test() = %s\n", $t->test());
}

// Below is what I expect to happen with a parent > child relationship
class CorrectScopeChild extends CorrectScope
{
	// Cannot reduce to protected/private
	public function __construct()
	{
		printf("%s\n", __METHOD__);
	}
	
	// Cannot reduce to protected/private
	public function test()
	{
		parent::test();
		printf("%s\n", __METHOD__);
	}
}

$correct = new CorrectScopeChild();
$correct->test();

?>

So this adds another question.  How do we deal with interfaced classes that don't have access to the contracted methods?  If we can't try/catch the calls resulting in fatal termination, I consider this to be a bug.
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Sun Mar 24 15:01:26 2019 UTC