PHP - Object instantiation context - Odd behaviour - Is it PHP bug? - php

I am not asking a typical question about why some code failed, yet I am asking about why it worked.It has worked with me while coding, and I needed it to fail.
Case
a base abstract class with a protected constructor declared abstract
a parent class extends the abstract class with public constructor (Over ridding)
a child class extends the very same abstract class with a protected constructor
abstract class BaseClass {
abstract protected function __construct();
}
class ChildClass extends BaseClass {
protected function __construct(){
echo 'It works';
}
}
class ParentClass extends BaseClass {
public function __construct() {
new ChildClass();
}
}
// $obj = new ChildClass(); // Will result in fatal error. Expected!
$obj = new ParentClass(); // that works!!WHY?
Question
Parent class instantiates child class object, and it works!!
how come it does?
as far as I know,object cannot be instantiated if its constructor declared protected, except only internally or from within any subclasses by inheritance.
The parent class is not a subclass of the child class,it doesn't inherit a dime from it ( yet both extend the same base abstract class), so how come instantiation doesn't fail?
EDIT
This case only happens with an abstract BaseClass that has also an abstract constructor.If BaseClass is concerete, or if its protected constructor is not abstract, instantiation fails as expected.. is it a PHP bug?
For my sanity, I need really an explanation to why PHP behaves this way in this very specific case.
Thanks in advance

Why it works?
Because from inside ParentClass you have granted access to the abstract method from BaseClass. It is this very same abstract method which is called from ChildClass, despite its implementation is defined on itself.
All relies in the difference between a concrete and an abstract method.
You can think like this: an abstract method is a single method with several implementations. On the other hand, each concrete method is a unique method. When it has the same name than its parent, it overrides the parent's one (it does not implement it).
So, when declared abstract, it is always the base class method which is called.
Think about a method declared abstract: Why the signatures of different implementations can't differ? Why can't the child classes declare the method with less visibility?
Anyway, you have just found a very interesting feature. Or, if my understanding above is not correct, and your expected behaviour is the truly expected behaviour, then you have found a bug.

Note: the following was tested with PHP 5.3.8. Other versions may exhibit different behavior.
Since there isn't a formal specification for PHP, there isn't a way of answering this from the point of view of what should happen. The closest we can get is this statement about protected from the PHP manual:
Members declared protected can be accessed only within the class itself and by inherited and parent classes.
Though the member may be overridden in ChildClass (keeping the "protected" specifier), it was originally declared in BaseClass, so it remains visible in descendants of BaseClass.
In direct opposition to this interpretation, compare the behavior for a protected property:
<?php
abstract class BaseClass {
protected $_foo = 'foo';
abstract protected function __construct();
}
class MommasBoy extends BaseClass {
protected $_foo = 'foobar';
protected function __construct(){
echo __METHOD__, "\n";
}
}
class LatchkeyKid extends BaseClass {
public function __construct() {
echo 'In ', __CLASS__, ":\n";
$kid = new MommasBoy();
echo $kid->_foo, "\n";
}
}
$obj = new LatchkeyKid();
Output:
In LatchkeyKid:
MommasBoy::__construct
Fatal error: Cannot access protected property MommasBoy::$_foo in - on line 18
Changing the abstract __construct to a concrete function with an empty implementation gives the desired behavior.
abstract class BaseClass {
protected function __construct() {}
}
However, non-magic methods are visible in relatives, whether or not they're abstract (most magic methods must be public).
<?php
abstract class BaseClass {
abstract protected function abstract_protected();
protected function concrete() {}
}
class MommasBoy extends BaseClass {
/* accessible in relatives */
protected function abstract_protected() {
return __METHOD__;
}
protected function concrete() {
return __METHOD__;
}
}
class LatchkeyKid extends BaseClass {
function abstract_protected() {}
public function __construct() {
echo 'In ', __CLASS__, ":\n";
$kid = new MommasBoy();
echo $kid->abstract_protected(), "\n", $kid->concrete(), "\n";
}
}
$obj = new LatchkeyKid();
Output:
In LatchkeyKid:
MommasBoy::abstract_protected
MommasBoy::concrete
If you ignore the warnings and declare magic methods (other than __construct, __destruct and __clone) as protected, they appear to be accessible in relatives, as with non-magic methods.
Protected __clone and __destruct are not accessible in relatives, whether or not they're abstract. This leads me to believe the behavior of abstract __construct is a bug.
<?php
abstract class BaseClass {
abstract protected function __clone();
}
class MommasBoy extends BaseClass {
protected function __clone() {
echo __METHOD__, "\n";
}
}
class LatchkeyKid extends BaseClass {
public function __construct() {
echo 'In ', __CLASS__, ": \n";
$kid = new MommasBoy();
$kid = clone $kid;
}
public function __clone() {}
}
$obj = new LatchkeyKid();
Output:
In LatchkeyKid:
Fatal error: Call to protected MommasBoy::__clone() from context 'LatchkeyKid' in - on line 16
Access to __clone is enforced in zend_vm_def.h (specifically, ZEND_CLONE opcode handler). This is in addition to access checks for methods, which may be why it has different behavior. However, I don't see special treatment for accessing __destruct, so there's obviously more to it.
Stas Malyshev (hi, Stas!), one of the PHP developers, took a look into __construct, __clone and __destruct and had this to say:
In general, function defined in base class should be accessible to all
[descendents] of that class. The rationale behind it is that if you define
function (even abstract) in your base class, you saying it will be
available to any instance (including extended ones) of this class. So
any descendant of this class can use it.
[...] I checked why ctor behaves differently, and it's because parent ctor
is considered to be prototype for child ctor (with signature
enforcement, etc.) only if it's declared abstract or brought from the
interface. So, by declaring ctor as abstract or making it part of the
interface, you make it part of the contract and thus accessible to all
hierarchy. If you do not do that, ctors are completely unrelated to each
other (this is different for all other non-static methods) and thus
having parent ctor doesn't say anything about child ctor, so parent
ctor's visibility does not carry over. So for ctor is not a bug. [Note: this is similar to J. Bruni's answer.]
I still think it's most probably a bug for __clone and __destruct.
[...]
I've submitted bug #61782 to track the issue with __clone and __destruct.

EDIT: constructors act differenlty... It's expected to work even without abstract classes but I found this test that tests the same case and it looks like it's a technical limitation - the stuff explained below doesn't work with constructors right now.
There's no bug. You need to understand that access attributes work with objects' context. When you extend a class, your class will be able to see methods in BaseClass' context. ChildClass and ParentClass both in BaseClass context, so they can see all BaseClass methods. Why do you need it? For polymorphism:
class BaseClass {
protected function a(){}
}
class ChildClass extends BaseClass {
protected function a(){
echo 'It works';
}
}
class ParentClass extends BaseClass {
public function b(BaseClass $a) {
$a->a();
}
public function a() {
}
}
No matter what child you pass into ParentClass::b() method, you'll be able to access BaseClass methods (including protected, because ParentClass is BaseClass child and children can see protected methods of their parents). The same behaviour applies to constructors and abstract classes.

I wonder if there isn't something buggy w/ the abstract implementation under the hood, or if there is a subtle issue going on that we're missing. Changing BaseClass from abstract to concrete produces the fatal error you're after though (classes renamed for my sanity)
EDIT: I agree w/ what #deceze is saying in his comments, that it is an edge case of abstract implementation and potentially a bug. This is at least a work-around that provides the expected behavior albiet some ugly technique (feigned abstract base class).
class BaseClass
{
protected function __construct()
{
die('Psuedo Abstract function; override in sub-class!');
}
}
class ChildClassComposed extends BaseClass
{
protected function __construct()
{
echo 'It works';
}
}
// Child of BaseClass, Composes ChildClassComposed
class ChildClassComposer extends BaseClass
{
public function __construct()
{
new ChildClassComposed();
}
}
PHP Fatal error: Call to protected ChildClassComposed::__construct()
from context 'ChildClassComposer' in
/Users/quickshiftin/junk-php/change-private-of-another-class.php on
line 46

Related

get_class_vars() in an abstract class returns wrong variables

I coded a bunch of classes extending an abstract class in PHP. The abstract class has variables as well as the class which extends the abstract class.
I would like to create a method inside the abstract class, which return all the class variables of the child classes but don't have to be recoded in every subclass.
This snippet works fine in a subclass in order to get all variables, the ones from the abstract class and the other classes:
get_class_vars(get_class($this))
However, if I move this snippet to the abstract class, it doesnt work. Here's what I did:
public function test($test)
{
var_dump(get_class($test));
var_dump(get_class_vars(get_class($test)));
}
This code returns the class name of the passed class correctly, but the get_class_vars() does only return the variables of the abstract class, no matter which class is passed here.
What did I do wrong here?
<?php
abstract class Entity
{
protected int $top;
public function test()
{
var_dump(get_called_class());
var_dump(get_class_vars(get_called_class()));
}
}
class Sub extends Entity
{
public String $test; // CHANGED FROM PRIVATE TO PUBLIC!
}
$test = new Sub();
$test->test();
I found the solution - it was a "private" issue. The variable in the subclass needs to be at least a protected variable in order to be seen from the top class.

Abstract methods visibility in PHP Object-oriented programming

I have an abstract class and I don't know the difference between the two ways of defining the test() function
abstract class Foo {
abstract protected function test();
}
and this
abstract class Foo {
abstract function test();
}
Does it make any difference?
From the PHP manual:
Class methods may be defined as public, private, or protected. Methods declared without any explicit visibility keyword are defined as public.
So the answer is no, they are not the same:
abstract protected function test(); will be accessible only within the class itself and by inheriting and parent classes;
abstract function test(); can be accessed from everywhere.
For compatibility with PHP 4 (where everything is public and there is no way to specify something else), the default visibility of class properties and methods is public.
Because of this,
abstract class Foo {
abstract function test();
}
is the same as:
abstract class Foo {
abstract public function test();
}

Abstract class method declaration

I just wrote code like this:
<?php
class test
{
// Force Extending class to define this method
abstract protected function getValue();
abstract protected function prefixValue($prefix);
// Common method
public function printOut() {
print $this->getValue() . "\n";
}
}
class testabs extends test{
public function getValue()
{
}
public function prefixValue($f)
{
}
}
$obj = new testabs();
?>
When I run this code, I received the error below:
Fatal error: Class test contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (test::getValue, test::prefixValue) in C:\wamp64\www\study\abstract.php on line 12
I understand the first part of this error. I changed the class test to abstract and the error is gone, but the or part i can't understand.
If you are going to add abstract methods, then you will need to make the class abstract as well. That way, the class cannot be instantiated- only non-abstract sub-classes can be.
The method visibility (refer to the second sub-section Method Visiblilty) is not the same in the sub-class. Depending on whether you want the methods to be called by code outside of sub-classes, you can declare the (abstract) methods in class test with visibility public, or else declare the sub-class methods also with visibility protected.
And note the second paragraph from the Class Abstraction page, which explains this:
When inheriting from an abstract class, all methods marked abstract in the parent's class declaration must be defined by the child; additionally, these methods must be defined with the same (or a less restricted) visibility. For example, if the abstract method is defined as protected, the function implementation must be defined as either protected or public, but not private
<?php
abstract class test{
// Force Extending class to define this method
abstract protected function getValue();
abstract protected function prefixValue($prefix);
// Common method
public function printOut() {
print $this->getValue() . "\n";
}
}
class testabs extends test{
protected function getValue()
{
}
/**
* this method can be called from other methods with this class
* or sub-classes, but not called directly by code outside of this class
**/
protected function prefixValue($f)
{
}
}
$obj = new testabs();
// this method cannot be called here because its visibility is protected
$obj->prefixValues();// Fatal Error
?>
The key technical differences between an abstract class and an interface are:
Abstract classes can have constants, members, method stubs (methods without a body) and defined methods, whereas interfaces can only have constants and methods stubs.
Methods and members of an abstract class can be defined with any visibility, whereas all methods of an interface must be defined as public (they are defined public by default).
When inheriting an abstract class, a concrete child class must define the abstract methods, whereas an abstract class can extend another abstract class and abstract methods from the parent class don't have to be defined.
Similarly, an interface extending another interface is not responsible for implementing methods from the parent interface. This is because interfaces cannot define any implementation.
A child class can only extend a single class (abstract or concrete), whereas an interface can extend or a class can implement multiple other interfaces.
A child class can define abstract methods with the same or less restrictive visibility, whereas a class implementing an interface must define the methods with the exact same visibility (public).
Your class has abstract functions but is not declared as abstract, so you have two choices. Either declare the class as abstract or provide an implementation of the abstract functions.
The first option (which you tried) allows the class to exist and be used by a concrete subclass that implements the functions. The second option means that the class is fully defined and can be used as is.
When your class has abstract methods it has to be declared abstract too.
So the following is correct:
<?php
abstract class test
{
// Force Extending class to define this method
abstract protected function getValue();
abstract protected function prefixValue($prefix);
// Common method
public function printOut() {
print $this->getValue() . "\n";
}
}

Why does Magento have _construct and __construct methods?

Is there a reason why Magento has a _construct and a __construct method? Why does the additional _construct exist? Could anything achieved by having the extra _construct method not be achieved by just calling the parent constructor in the child class?
Best answer I can find: http://www.magentocommerce.com/boards/viewthread/76027/#t282659
Basically, the root-level class (from which all other classes inherit) implements __construct, which PHP calls automatically whenever a class is constructed. Right now, this root-level class simply calls _construct, which contains the actual code.
Say you have this set-up:
class BaseClass {
function __construct() {
print "In BaseClass constructor\n";
doSomethingReallyImportant();
}
}
class SubClass extends BaseClass {
function __construct() {
print "In SubClass constructor\n";
}
}
$obj = new BaseClass();
//"In BaseClass constructor"
//something really important happens
$obj = new SubClass();
//"In SubClass constructor"
//important thing DOESN'T happen
PHP doesn't automatically call the parent class constructors, so doSomethingReallyImportant never gets called. You could require that subclass constructors call parent::__construct(), but that's easy to forget. So Magento has subclasses override _construct:
class BaseClass {
function __construct() {
doSomethingReallyImportant();
_construct();
}
function _construct() {
print "In BaseClass constructor\n";
}
}
class SubClass extends BaseClass {
function _construct() {
print "In SubClass constructor\n";
}
}
$obj = new BaseClass();
//something really important happens
//"In BaseClass constructor"
$obj = new SubClass();
//something really important happens
//"In SubClass constructor"
PHP doesn't detect a constructor in SubClass, so it calls BaseClass's constructor. This allows BaseClass to doSomethingReallyImportant before calling SubClass's overridden _construct.
To Marco: it is wrong to override __construct() method like this in Magento. The reason is - all classes inherit it from Varien_Object and it has this code:
#File: lib/Varien/Object.php
public function __construct()
{
//...snip...
$args = func_get_args();
if (empty($args[0]))
{
$args[0] = array();
}
//...snip...
}
//...
With the __construct using your code, those arguments don’t get passed through.
You really have to use Benesch's code:
class SubClass extends BaseClass {
function _construct() {
print "In SubClass constructor\n";
}
}
Read more about this in Magento Block Lifecycle Methods by Alan Storm
Edit: sorry, missed the the difference between _construct and __construct in your question. I think the Magento programmers have tried to make it easier to override the constructor without the risk of their own constructor not being called anymore. The _construct method on Varien_Object is empty so it doesn't matter if it's not called from subclasses.
This is just how PHP implements constructors and destructors for classes. There's nothing Magento specific about it.
In other languages the constructor is usually implemented with a method having the same name as the class itself and the constructor usually has a tilde (~) in front of the method name baring the same name as the class. For some reason the PHP people have implemented it this way although PHP also seem to support constructors and destructors with the class name (link).
A class does not have to have a constructor and/or destructor, especially when you subclass another class. If you do override the constructor or destructor then you need to call the constructor or destructor of the overridden class manually by calling it on parent::, like:
class SubClass extends BaseClass {
function __construct() {
parent::__construct();
// Your code
}
function __destruct() {
// Your code
parent::__destruct();
}
}
Single underscore construct (_construct) is used to avoid overriding the actual constructor with double underscore (__construct).
Example: vendor/magento/framework/Model/ResourceModel/AbstractResource.php
/**
* Constructor
*/
public function __construct()
{
/**
* Please override this one instead of overriding real __construct constructor
*/
$this->_construct();
}
/**
* Resource initialization
*
* #return void
*/
abstract protected function _construct();

Why can't you call abstract functions from abstract classes in PHP?

I've set up an abstract parent class, and a concrete class which extends it. Why can the parent class not call the abstract function?
//foo.php
<?php
abstract class AbstractFoo{
abstract public static function foo();
public static function getFoo(){
return self::foo();//line 5
}
}
class ConcreteFoo extends AbstractFoo{
public static function foo(){
return "bar";
}
}
echo ConcreteFoo::getFoo();
?>
Error:
Fatal error: Cannot call abstract method AbstractFoo::foo() in foo.php on line 5
This is a correct implementation; you should use static, not self, in order to use late static bindings:
abstract class AbstractFoo{
public static function foo() {
throw new RuntimeException("Unimplemented");
}
public static function getFoo(){
return static::foo();
}
}
class ConcreteFoo extends AbstractFoo{
public static function foo(){
return "bar";
}
}
echo ConcreteFoo::getFoo();
gives the expected "bar".
Note that this is not really polymorphism. The static keywork is just resolved into the class from which the static method was called. If you declare an abstract static method, you will receive a strict warning. PHP just copies all static methods from the parent (super) class if they do not exist in the child (sub) class.
You notice that word self?
That is pointing to AbstractClass. Thus it is calling AbstractClass::foo(), not ConcreteClass::foo();
I believe PHP 5.3 will provide late static bindings, but if you are not on that version, self will not refer to an extended class, but the class that the function is located in.
See: http://us.php.net/manual/en/function.get-called-class.php
It's a rule that abstract and static keywords can not be use on a method at the same time.
A method with an abstract keyword means that sub-class must implement it. Adding static to a method of a class allows us to use the method without instantiating it.
So that is why the error occurs.

Categories