I found a curious fatal error in my application today. From what I can tell, I'm not modifying this property any where. However, when I pass it to the parent constructor I get a "Cannot modify readonly property" error.
The purpose of the code is to build a factory for a variety of xml import structures. The application is a little immature right now but this error is confusing me.
Any thoughts out there?
PHP Fatal error: Uncaught Error: Cannot modify readonly property ImportFactoryAbstract::$appConfig in test.php:25
Stack trace:
#0 test.php(46): XmlLeafListAbstract->__construct(Object(AppConfig), Array, Array)
#1 test.php(60): ImportFactoryAbstract->__construct(Object(AppConfig), Array)
#2 {main}
thrown in test.php on line 25
This is ae-php83
<?php
class AppConfig implements \ArrayAccess
{
private array $config = [];
// ArrayAccess methods
public function offsetExists(mixed $offset): bool { return isset($this->config[$offset]); }
public function offsetGet(mixed $offset): mixed { return $this->offsetExists($offset) ? $this->config[$offset] : null; }
public function offsetSet(mixed $offset, mixed $value): void { /* readonly data */ }
public function offsetUnset(mixed $offset): void { /* readonly data */ }
// do ini config stuff
}
abstract class XmlLeafListAbstract extends \ArrayObject
{
abstract public function createList(): XmlLeafListAbstract;
/**
* @param AppConfig $appConfig
* @param array $unhandledXmlElements
* @param array $list
*/
public function __construct( // line 25
protected readonly AppConfig $appConfig,
protected array &$unhandledXmlElements,
protected readonly array $list,
)
{
parent::__construct();
$this->createList();
}
}
abstract class ImportFactoryAbstract extends XmlLeafListAbstract
{
public function __construct(
protected readonly AppConfig $appConfig,
protected readonly array $list,
)
{
// do some stuff
$unhandledXmlElements = [];
parent::__construct($this->appConfig, $unhandledXmlElements, $this->list);
// do some more stuff
}
}
class SomeImport extends ImportFactoryAbstract
{
public function createList(): XmlLeafListAbstract{
// do some business logic
return $this;
}
}
$bug = new SomeImport(new AppConfig(), $xmlObj ?? []);
print "Successfully did the thing!\n";
I'm reiterating what the other two said. The problem is because I think you may be misunderstanding what is actually happening... Writing in the pre-8.0 long form shows you some of what's happening behind the scenes. You are accessing the same property twice because of how visibility works with inheritance.
Here is a simplified version of what you are doing
<?php
class a{
function __construct(protected readonly int $a) {
}
}
class b extends a {
function __construct(protected readonly int $a){
parent::__construct($this->a);
}
}
$b = new b(1);
Here it is expanded not using short hand property declaration... These 2 pieces of code are equivalent.
<?php
class a{
protected readonly int $a;
function __construct($a) {
$this->a=$a;
}
}
class b extends a {
protected readonly int $a;
function __construct($a){
$this->a=$a;
parent::__construct($this->a);
}
}
$b = new b(1);
Note the code fails on a::__construct() because it's the second time we assign property $a.
Here is the code working as we suggest with the removal of redeclaring property $a.
<?php
class a{
protected readonly int $a;
function __construct($a) {
$this->a=$a;
}
}
class b extends a {
function __construct($a){
parent::__construct($a);
}
}
$b = new b(1);
Or in short hand...
<?php
class a{
function __construct(protected readonly int $a) {
}
}
class b extends a {
function __construct(int $a){
parent::__construct($a);
}
}
$b = new b(1);
Here is your code using expanded property notation and comments on how to fix it... So really if you want to leave it as shorthand don't put the full declaration of properties in your child class ImportFactoryAbstract
<?php
class AppConfig implements \ArrayAccess
{
private array $config = [];
// ArrayAccess methods
public function offsetExists(mixed $offset): bool { return isset($this->config[$offset]); }
public function offsetGet(mixed $offset): mixed { return $this->offsetExists($offset) ? $this->config[$offset] : null; }
public function offsetSet(mixed $offset, mixed $value): void { /* readonly data */ }
public function offsetUnset(mixed $offset): void { /* readonly data */ }
// do ini config stuff
}
abstract class XmlLeafListAbstract extends \ArrayObject
{
abstract public function createList(): XmlLeafListAbstract;
// expanded out property declaration.
protected readonly AppConfig $appConfig;
protected array $unhandledXmlElements;
protected readonly array $list;
/**
* @param AppConfig $appConfig
* @param array $unhandledXmlElements
* @param array $list
*/
public function __construct( // line 25
AppConfig $appConfig,
array &$unhandledXmlElements,
array $list,
)
{
// doing what short handing the constructor would do.
// since this would be the second time the property is set... it's no bueno.
$this->appConfig = $appConfig;
$this->unhandledXmlElements = $unhandledXmlElements;
$this->list = $list;
parent::__construct();
$this->createList();
}
}
abstract class ImportFactoryAbstract extends XmlLeafListAbstract
{
// expanded out property declaration.
protected readonly AppConfig $appConfig;
protected readonly array $list;
public function __construct(
AppConfig $appConfig,
array $list
)
{
// doing what short handing the constructor would do.
// go ahead and remove this and just pass appconfig and list to the parent constructor...
$this->appConfig = $appConfig;
$this->list = $list;
// do some stuff
$unhandledXmlElements = [];
// you woild remove $this-> here in the 2 properties.
parent::__construct($this->appConfig, $unhandledXmlElements, $this->list);
// do some more stuff
}
}
class SomeImport extends ImportFactoryAbstract
{
public function createList(): XmlLeafListAbstract{
// do some business logic
return $this;
}
}
$bug = new SomeImport(new AppConfig(), $xmlObj ?? []);
print "Successfully did the thing!\n";