php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #40880 public->protected inheritance causes fatal
Submitted: 2007-03-21 09:38 UTC Modified: 2012-05-07 13:26 UTC
From: prometheus__0 at hotmail dot com Assigned:
Status: Not a bug Package: Class/Object related
PHP Version: 5CVS-2007-03-21 (snap) OS: SUSE SLES 10
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: prometheus__0 at hotmail dot com
New email:
PHP Version: OS:

 

 [2007-03-21 09:38 UTC] prometheus__0 at hotmail dot com
Description:
------------
(1) It is not possible to make inherited functions more private, which seems like a bug to me but
(2) it is possible to make inherited functions more public, which shouldn't be possible, afaik.

The example code causes the fatal (1)
if you change
protected function __construct()
to
public function __construct()
from class b

and
public function __construct(){
to
protected function __construct(){
from class a
it works (2)

since i'm not an expert in oop i tried the same example in java and it works the complete opposite way (the b functions can be more private but not more public)
and in C++ it's the same
i know bug report http://bugs.php.net/bug.php?id=34237 but it considers point (2)
point (1) is still a bug in my opinion

Reproduce code:
---------------
<?php
class a{
    public function __construct(){
        print("public construct\n");
    }
}

class b extends a{
    protected function __construct(){
        print("protected construct\n");
    }
    
    public static function getInstance(){
        return new b();
    }
}

$b = b::getInstance();
?>

Expected result:
----------------
protected construct

Actual result:
--------------
Fatal error:  Access level to b::__construct() must be public (as in class a) in PHPDocument2 on line 16

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2007-03-21 09:43 UTC] derick@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

This is how it works... you can always open up an API through a new extended interface, but not hide more.
 [2007-03-21 10:05 UTC] prometheus__0 at hotmail dot com
is this the 'php'-dev definition?
i'm asking cause wraping a singleton pattern around a subclass makes sense
and the same example is valid for java and c++
to ask it differently: why is it working this way in php? (i'm interested in the background of this)
my point is that 2 languages allow it and there is an example which is valid, not?
 [2010-09-07 12:58 UTC] nickyla83 at yahoo dot fr
I'm in the same situation as our friend "prometheus" here, I am trying to use a singleton pattern and logically, this should involve being able to encapsulate the subcalasses information particularly setting up a private constructor for the singleton subclass, IT DEFINITELY DOES MAKE SENSE, so please try to take this under consideration for the next php engine release.
 [2010-10-15 23:33 UTC] robertoblanko at gmail dot com
Same problem here. You cannot actually apply the singleton pattern to subclasses with this behavior. I do not see any reason for not calling this a bug.
 [2011-09-10 22:27 UTC] herman dot wetherington at gmail dot com
Apparently, we don't call this a "bug" because this is not caused by a programming error. So I'm calling it a "massive design flaw" instead.
 [2012-05-06 15:40 UTC] ekincigokan at gmail dot com
Same "problem".

class A{
    public function __construct(){}
}

class B extends A {
    protected function __construct(){}
}

Please, can you tell us a reason why we can't put an private/protected access to B's constructor if A's constructor is public ?
Is there any logical reason to this ?


Thanks.
 [2012-05-06 16:12 UTC] rasmus@php.net
Most (all?) object oriented languages work this way. Java and C# both do. You can 
loosen visibility when you override a parent method, but you can never tighten 
it. This is part of what is known as the Liskov Substitution Principle and it is 
one of the fundamental principles of object oriented programming. You can read 
about at http://en.wikipedia.org/wiki/Liskov_substitution_principle

I can guarantee that abiding by LSP is not a bug
 [2012-05-07 08:14 UTC] postmaster at greg0ire dot fr
@rasmus:
Then why is there a strict warning regarding the compatibility of method signatures for this code:

--------------
class Foo {
    public function test() {
    }
}
 
class Bar extends Foo {
    public function test(ArrayObject $arrayObj, $number = 0) {
        /* do stuff with $arrayObj and $number */
    }
}

---------------

and not for this one:

---------------
class Foo {
    public function __construct() {
    }
}
 
class Bar extends Foo {
    public function __construct(ArrayObject $arrayObj, $number = 0) {
        /* do stuff with $arrayObj and $number */
    }
}
------------

No, as stated here : 
- http://stackoverflow.com/questions/5490824/should-constructors-comply-with-the-liskov-substitution-principle 
- and here: http://ralphschindler.com/2012/03/09/php-constructor-best-practices-and-the-prototype-pattern, the LSP does not apply to constructors for several reasons:
- When you call the constructor, you know whether you're using the type or one of its subtypes.
- You can't apply the LSP to an object that does not exist yet.

I think this is a design flaw. Not "massive", but a design flaw indeed.
 [2012-05-07 09:49 UTC] pajoye@php.net
@postmaster at greg0ire dot fr

__construct applies only to the class where it is declared.

On the other hand, a method, already defined in the parent class, must be as 
visible as the one declared in the parent class and with a compatible signature. 
The 2nd clause is not respected in your example as the 1st argument is not 
optional. It would work if it was optional, making $a->test() possible ass it is 
with the parent class.

All these details are very explained in the PHP manual and other various OOP 
references out there.
 [2012-05-07 12:17 UTC] postmaster at greg0ire dot fr
@pajoye : let me explain better what I tried to point out there:

Rasmus says the Fatal error we're getting is here to enforce the LSP.
What I'm saying here is that I'm not getting a signature-compatibility strict notice with the __construct() example , so obviously, the developer who wrote this notice thinks that LSP should not apply to __construct() (and I agree with him).

This is just one more argument to convince Rasmus that the LSP does not apply here : obviously, even some member of the php team are aware of that.

So to sum up the rules you are talking about, though "explained in the PHP manual", should not apply to __construct() (please read the references I gave in my previous comment to understand why).
 [2012-05-07 13:26 UTC] rasmus@php.net
This bug report isn't about constructors specifically though. See the bug title 
and the opening points (1) and (2). The fact that __construct was used as an 
example seemed entirely coincidental here.

For constructors specifically, I agree that Liskov can be mostly ignored, and we 
do so as you have discovered. The one case where we haven't is visibility. It 
seems a bit of an edge-case to tighten visibility on an inherited constructor 
and if that discourages the singleton pattern we might be doing the world a 
favour here.
 [2012-05-07 18:45 UTC] postmaster at greg0ire dot fr
I filed a separate bug report here: https://bugs.php.net/bug.php?id=61970 so that this bug report does not get too cluttered.

I agree that Singleton is not the best of the design patterns, but what if someone wants to use it anyway? And what if someone needs a protected / private constructor for some reason I cannot imagine?
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Mon Nov 25 15:01:32 2024 UTC