PhpSpreadsheet icon indicating copy to clipboard operation
PhpSpreadsheet copied to clipboard

Error: Typed property PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::$parent must not be accessed before initialization

Open pan85 opened this issue 1 year ago • 5 comments

What is the expected behavior?

private ?Spreadsheet $parent = null;

What is the current behavior?

private ?Spreadsheet $parent;

Which versions of PhpSpreadsheet and PHP are affected?

2.0.0

pan85 avatar Apr 18 '24 07:04 pan85

Do you have code which demonstrates that the current code fails as described?

oleibman avatar Apr 18 '24 14:04 oleibman

I have the same issue happening on destruct a mocked Worksheet.

object(TestStub_Worksheet_87f22e33)#748 (39) {
  ["parent":"PhpOffice\PhpSpreadsheet\Worksheet\Worksheet":private]=>
  uninitialized(?PhpOffice\PhpSpreadsheet\Spreadsheet)
...
Error: Typed property PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::$parent must not be accessed before initialization in /app/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php:380
Stack trace:
#0 /app/src/XXXHandler.php(54): PhpOffice\PhpSpreadsheet\Worksheet\Worksheet->__destruct()
#1 /app/tests/XXXHandlerTest.php(105): XXXHandler->__invoke(Object(XXXCommand))

For me it gets fixed by initializing the parent property on class level with null as it's nullable anyway:

BEFORE:  private ?Spreadsheet $parent;
AFTER:   private ?Spreadsheet $parent = null;

marc-mabe avatar May 16 '24 10:05 marc-mabe

@marc-mabe Can you share more of your code? Based on your description, I tried:

        $body = $this->getMockBuilder(Worksheet::class)
                    ->disableOriginalConstructor()
                    ->getMock();
        $body = null;sleep(5);
        gc_collect_cycles();

It did not throw an exception/error.

Even if the mock method were to work in simulating the problem, I'm just having trouble understanding how it can happen "natively". The first statement in the constructor initializes the property that is supposedly uninitialized. There is no unset statement for the property. How does it ever get to a state where the property is uninitialized?

oleibman avatar May 17 '24 04:05 oleibman

Hi @oleibman,

I'm also unsure why exactly it happens but I'm not actively creating the mock but a sheet creator service and testing a specific function (creating the worksheet) to be called.

It looks more like this (shorten as much as possible - not tested):

class SheetCreator {
    public function createInfoSheet(Spreadsheet $workbook, $info): Worksheet
    {
        $worksheet = $workbook->createSheet();
        $worksheet->setTitle($info);
        // ...
        return $worksheet;
    } 
}

class MyHandler {
    public function __construct(
        private readonly CreateTableHandler $createTableHandler,
        private readonly SheetCreator $infoSheetCreator,
    ) {}

    public function __handle(MyCommand $command):void {
        $table = $this->createTableHandler->__invoke($command->whatever);
        $workbook = TableToPhpSpreadsheet::createSpreadsheet($table);
        $this->infoSheetCreator->createInfoSheet($workbook, $info);
        // ...
        $xlsx = new Xlsx($workbook);
        $xlsx->save($file);
    }
}

class MyHandlerTest {
    public function testHandler(): void
    {
        $tableCreatorHandlerMock = $this->createMock(CreateTableHandler::class);
        $creatorMock = $this->createMock(SheetCreator::class);
        $handler  = new MyHandler($creatorMock);

        // ...
        $table = new MyTable();
        $tableCreatorHandlerMock
            ->expects(static::exactly(1))
            ->method('__invoke')
            ->with($whatever)
            ->willReturn($table);

         $creatorMock
            ->expects(static::exactly(1))
            ->method('createInfoSheet')
            ->with(static::isInstanceOf(Spreadsheet::class), $info);

        // ...
        $handler->__invoke($command);
        // ....
    }
}

marc-mabe avatar May 17 '24 06:05 marc-mabe

I apologize, I am not able to get your code into a state where I can work with it, e.g. CreateTableHandler class is not defined, ditto for MyTable, MyHandler constructor requires 2 arguments but you supply only 1, no idea what command should be on the last line with code, and more. Are you able to flesh it out a bit to something that I can run?

oleibman avatar May 22 '24 03:05 oleibman

Good news! We currently use Phpunit 9 for our unit tests, but, now that we support only Php 8.1+, Dependabot recommended a switch to Phpunit 10. This requires some changes, mostly to test members. But ... test Reader/Xlsx/AutoFilterTest, which indeed uses mocking, failed with "must not be accessed before initialization"! And the suggested change eliminates that error. So expect a fix when I'm ready to push the Phpunit change.

oleibman avatar Jun 01 '24 23:06 oleibman

Ran into this issue today, glad to see it has already been solved ! May i ask if a patch release is scheduled soon ?

AirBair avatar Jun 21 '24 08:06 AirBair

New release happened this week.

oleibman avatar Jul 27 '24 15:07 oleibman