Under what circumstances is it possible that parent fields are not inherited? - php

I've got a curious bug (bugging me) today. There are three inheritance levels involved:
Grandpa:
abstract class Zend_Db_Table_Row_Abstract implements ArrayAccess,
IteratorAggregate
{
protected $_data = array();
/* snip */
}
Mom:
namespace Survey\Db\Table\Row;
class AbstractRow extends \Zend_Db_Table_Row_Abstract
{
/* snip */
}
Child:
namespace Survey\Db\Table\Row;
class SurveyItem extends AbstractRow implements ISkippable
{
/* snip */
}
Exception:
Type: ErrorException
Value: Undefined property: Survey\Db\Table\Row\SurveyItem::$_data
Location: [...]/Zend/Db/Table/Row/Abstract.php in handleError , line 177
Line 177 doesn't seem to be relevant, but I'm adding it just so you'd believe me ;)
if (!array_key_exists($columnName, $this->_data)) {
PHP 5.4.11, problem did NOT exist with PHP 5.4.8
When I saw the fix for Bug #63462 Magic methods called twice for unset protected properties, I thought, that wold solve the problem, since this bug leads to exactly the weird unexpected outcome I was seeing.
But it turns out, the problem still exists after updating to PHP 5.4.12. The likelyhood that there is another similar bug in PHP seems quite high.
Question:
I get the info that a protected field defined in the Grandpa is undefined in the Child. What scenarios can lead to such an outcome?

following snippet works flawlessly on PHP 5.4.9:
class A
{
protected $foo = 'hello';
public function bar()
{
echo $this->foo;
}
}
class B extends A {}
class C extends B {}
$c = new C();
$c->bar();
Please minimise your code step by step to this to see if/when problem occurs (I wonder why you haven't done it already)
If you are sure this worked on PHP 5.4.8 and does not work on PHP 5.4.11 then you found a bug in PHP and should be reporting it on php.net
Answer may be different (maybe it simply got 'unset' along the way).
Minimise your code and you will know.

If you don't want the parent field to inherit in the child class through object then declar the parent field as "static".

Related

How handle visibility change of property in class when using serialized objects?

UPDATE:
As someone says this question is a bit unclear. I couldn't really understand it myself. But I did dig up the solution. It's a PHP bug that has been resolved. The server this legacy code runs on has an outdated php version.
Old code:
class MyChildClass extends MyBaseClass
{
protected $myProp = "foo";
}
class MyBaseClass {
}
$myObject = new MyChildClass();
$myObjectS = serialize($myObject);
New code:
class MyChildClass extends MyBaseClass
{
}
class MyBaseClass {
public $myProp;
}
// $myObjectS is the same as in the Old code above.
$myObject = unserialize($myObjectS);
This will result in an object with a protected property $myProp with a value of "foo". But there will also be a public property with the same name with an undefined value.
I really do not know how to fix this. The result I want is to have the objects public property populated with the serialized objects value for its protected property.
I would not design the code like this with the serialization of complete objects. But this is a legacy project where I (for the time being) have to deal with this.
Not easy as you'll have to change all the occurencies of protected properties in $myObjects.
In your example {s:7:"*myProp";s:3:"foo";} become {s:6:"myProp";s:3:"foo";}.
Set length of myProp from 7 to 6, and remove the leading star of *myProp. Lot of work if you have lot of protected properties :/

Weird PHP behaviour, same field name for Base and Derived classes, cloned instance

Sorry for the confused title, I wasn't able to find a better one.
I'm on PHP/5.6.14, I have this code:
class Base
{
private $foo; <--- NOTE
public function __construct()
{
$this->foo = "base foo";
}
public final function getFoo()
{
return $this->foo;
}
}
class Derived extends Base
{
public $foo; <--- NOTE
public function __construct($type)
{
parent::__construct();
$this->foo = "derived foo";
$this->somethingUndefined = "dynamically declared"; <--- NOTE
}
}
$base = new Base();
var_dump($base->getFoo());
$derived = new Derived(0);
var_dump($derived->getFoo());
$clonedDerived = clone $derived; <--- NOTE
var_dump($clonedDerived->getFoo());
Running the last getFoo() gives me:
PHP Notice: Undefined property: Derived::$foo in C:..\test.php on
line *the line where getFoo() is implemented in the base class*.
If I put this three conditions together I get the notice:
same public/private field name
having one or more dynamically declared field in the derived class
working on a clone of an instance of the derived class
Removing one or more of them makes the notice go away.
What's going on here? $foo is not static, and it's not undefined...
I'm using a custom error_handler to turn every E_ALL to an exception and can't really ignore this...
EDIT:
This is just an example I wrote to reproduce the issue, real code is much different. I'm NOT trying to expose the private $foo or something. It just happened that Base and Derived were imlemented by different developers that choosed the same name for a field and doing that (along with the other two conditions) caused the Notice. I'm just trying to understand why, since AFAIK it should be perfectly fine.
I believe this to be a bug in PHP, since running that example in PHP7 does not produce this notice. Even more, if you var_dump($this) in getFoo() definition, it is clearly visible that "foo":"Base":private is defined, and foo of object Derived is defined as well, in both PHP >= 7 and PHP < 7. Please see: https://3v4l.org/Ob9m7
However, I believe that, whatever you are trying to do with this piece of code, you are going about it the wrong way. As ArtisticPhoenix already mentioned. I would strongly suggest you reconsider and re-design whatever you are doing. Overriding private members is just not possible as per OOP.
That is the expected behavior, private means this class only. Therefore the private property $foo is not visible to the child class. Changing it to public is not gonna work, likely there is a warning associated to that that you are not seeing. What level error reporting to you have on.
Child classes have to keep at least the same visibility of the parent.
At best you could do it private in the child class, but that is not going to give you access to the same $foo
Notice or not, the readability of the code is poor, when trying to force a private variable to be something else. Which foo do you expect to get? And why not just name it something else in the child class if you need $foo in the parent as a default, just do an if/then in the child's get method.
Essentially your trying to change the value of $foo in the child class when it's private in the parent class. And then retrieve it from the parent. As I mentioned at best you wont get the $foo you expect and it makes the code confusing.

Why is this method called without being asked to?

I came across this very strange behavior.
The following code
class TestClass {
function testClass() {
echo "Don't show me!";
}
}
$testing = new TestClass;
executes its method testClass without it being called!
However, testClass won't run if renamed into anything else like testClass1.
Is there any hidden 'PHP magic' behind this behaviour?
EDIT.
At the end I see this question is trivial to ninjas grown up with PHP. As recent newcomer to PHP, I've learned to use __construct as constructor. With that "relic behaviour" carefully removed from modern tutorials. I am so glad people realized how terrible it was, changing class name and forgetting to change that of the constructor - what a nightmare!
Pre-PHP5, the __construct method was not used as the class constructor. Instead, a method with the same name as the class was used.
From the documentation:
For backwards compatibility, if PHP 5 cannot find a __construct() function for a given class, and the class did not inherit one from a parent class, it will search for the old-style constructor function, by the name of the class. Effectively, it means that the only case that would have compatibility issues is if the class had a method named __construct() which was used for different semantics.
Creating a (empty) constructor (method named __construct) will stop the message from being echoed upon class initialization (only needed for < PHP 5.3.3 *):
class TestClass {
function __construct() {
}
function testClass() {
echo "Don't show me!";
}
}
* As of PHP 5.3.3, methods with the same name as the last element of a namespaced class name will no longer be treated as constructor. This change doesn't affect non-namespaced classes.
In older versions of PHP a method with the same name as the classname was considered the constructor.

Can parameter types be specialized in PHP

Say we've got the following two classes:
abstract class Foo {
public abstract function run(TypeA $object);
}
class Bar extends Foo {
public function run(TypeB $object) {
// Some code here
}
}
The class TypeB extends the class TypeA.
Trying to use this yields the following error message:
Declaration of Bar::run() must be compatible with that of Foo::run()
Is PHP really this broken when it comes to parameter types, or am I just missing the point here?
This answer is outdated since PHP 7.4 (partially since 7.2).
The behavior you describe is called covariance and is simply not supported in PHP. I don't know the internals but I might suspect that PHP's core does not evaluate the inheritance tree at all when applying the so called "type hint" checks.
By the way, PHP also doesn't support contravariance on those type-hints (a feature commonly support in other OOP languages) - most likely to the reason is suspected above. So this doesn't work either:
abstract class Foo {
public abstract function run(TypeB $object);
}
class Bar extends Foo {
public function run(TypeA $object) {
// Some code here
}
}
And finally some more info: http://www.php.net/~derick/meeting-notes.html#implement-inheritance-rules-for-type-hints
This seems pretty consistent with most OO principals. PHP isn't like .Net - it doesn't allow you to override class members. Any extension of Foo should slide into where Foo was previously being used, which means you can't loosen constraints.
The simple solution is obviously to remove the type constraint, but if Bar::run() needs a different argument type, then it's really a different function and should ideally have a different name.
If TypeA and TypeB have anything in common, move the common elements to a base class and use that as your argument constraint.
I think this is by design: It is the point of abstract definitions to define the underlying behaviour of its methods.
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.
One could always add the constraint in code:
public function run(TypeA $object) {
assert( is_a($object, "TypeB") );
You'll have to remember or document the specific type limitation then. The advantage is that it becomes purely a development tool, as asserts are typically turned off on production servers. (And really this is among the class of bugs to be found while developing, not randomly disrupt production.)
The code shown in the question is not going to compile in PHP. If it did class Bar would be failing to honour the gurantee made by it's parent Foo of being able to accept any instance of TypeA, and breaching the Liskov Substitution Principle.
Currently the opposite code won't compile either, but in the PHP 7.4 release, expected on November 28 2019, the similar code below will be valid, using the new contravariant arguments feature:
abstract class Foo {
public abstract function run(TypeB $object); // TypeB extends TypeA
}
class Bar extends Foo {
public function run(TypeA $object) {
// Some code here
}
}
All Bars are Foos, but not all Foos are Bars. All TypeBs are TypeAs but not all TypeAs are TypeBs. Any Foo will be able to accept any TypeB. Those Foos that are also Bars will also be able to accept the non-TypeB TypeAs.
PHP will also support covariant return types, which work in the opposite way.
Although you cannot use whole class-hierarchies as type-hinting. You can use the self and parent keywords to enforce something similar in certain situations.
quoting r dot wilczek at web-appz dot de from the PHP-manual comments:
<?php
interface Foo
{
public function baz(self $object);
}
class Bar implements Foo
{
public function baz(self $object)
{
//
}
}
?>
What has not been mentioned by now is that you can use 'parent' as a typehint too. Example with an interface:
<?php
interface Foo
{
public function baz(parent $object);
}
class Baz {}
class Bar extends Baz implements Foo
{
public function baz(parent $object)
{
//
}
}
?>
Bar::baz() will now accept any instance of Baz.
If Bar is not a heir of any class (no 'extends') PHP will raise a fatal error:
'Cannot access parent:: when current class scope has no parent'.

PHP interface problem - class not found

Hi I have a very simple class that implements an interface. Both the class and the interface are in the same file.
When I implement the interface I get a fatal error "Class not found", but when I remove the implements and then try to use the class I can use it fine???
Can anyone offer any advice on this?
Sorry here is some code that I am using to test at the moment:
$tester = new TypeOneTester();
$tester->test("Hello");
interface iTestInterface
{
public function test($data);
}
class TypeOneTester implements iTestInterface
{
public function test($data)
{
return $data;
}
}
Create an instance of your class after the class and the interface are defined, not before.
The order of definition in this case should be:
Interface
Class
Instance of Class (objects)
This is a (very poorly) documented limitation:
http://php.net/manual/pl/migration5.incompatible.php
In some cases classes must be declared before use. It only happens if some of the new features of PHP 5 (such as interfaces) are used. Otherwise the behaviour is the old.
I've filed a bug report nonetheless. IMO it should be fixed as it's inconsistent behaviour and the error message is not helpful for anyone who assumes as I did that PHP simply didn't care where you declare functions/classes. Come on, it's been there for over 10 years now...
https://bugs.php.net/bug.php?id=69665
smells like a bug in php. Make sure it's reproducible with the latest version and post to bugs.php.net.
Reproduce code
interface I {}
$a = new A;
$b = new B;
class A {
function __construct() { echo 'A'; }
}
class B implements I {
function __construct() { echo 'B'; }
}
Expected
AB
Actual:
A
Fatal error: Class 'B' not found...
That is because, php loading interface, and instantiate class class class object where there is a certain order and must be in a Php file, if the file is not in accordance with an order of 1. Require_one interface, 2. Require_one class

Categories