Skip to content

Commit

Permalink
[BUGFIX] Fix serialization of LazyLoadingProxy and LazyObjectStorage
Browse files Browse the repository at this point in the history
Due to the dependency of those objects to the DataMapper and its own
dependency chain, serialization of those objects lead to error:

Serialization of \Closure not allowed.

To ommit this issue and to reduce the footprint of the serialization
data in general, the data mapper should be omitted and reinitialized
via __serialize() and __unserialize()

Releases: main, 11.5
Resolves: #97190
Resolves: #92148
Change-Id: If8b78a93308eb14d40080c9cceee95754b785ba8
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/73975
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Stefan Bürk <stefan@buerk.tech>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Oliver Klee <typo3-coding@oliverklee.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
  • Loading branch information
alexanderschnitzler authored and sbuerk committed Nov 8, 2022
1 parent 971be90 commit bdba25c
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 5 deletions.
5 changes: 0 additions & 5 deletions Build/phpstan/phpstan-baseline.neon
Expand Up @@ -1730,11 +1730,6 @@ parameters:
count: 1
path: ../../typo3/sysext/extbase/Classes/Persistence/Generic/LazyObjectStorage.php

-
message: "#^Property TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\Generic\\\\LazyObjectStorage\\:\\:\\$warning is never read, only written\\.$#"
count: 1
path: ../../typo3/sysext/extbase/Classes/Persistence/Generic/LazyObjectStorage.php

-
message: "#^Property TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\Generic\\\\Mapper\\\\ColumnMap\\:\\:\\$relationTableWhereStatement is unused\\.$#"
count: 1
Expand Down
Expand Up @@ -243,4 +243,20 @@ public function valid(): bool
{
return $this->current() !== false;
}

public function __serialize(): array
{
$properties = get_object_vars($this);
unset($properties['dataMapper']);
return $properties;
}

public function __unserialize(array $data): void
{
foreach ($data as $propertyName => $propertyValue) {
$this->{$propertyName} = $propertyValue;
}

$this->dataMapper = GeneralUtility::getContainer()->get(DataMapper::class);
}
}
Expand Up @@ -320,4 +320,23 @@ public function getPosition($object): ?int
$this->initialize();
return parent::getPosition($object);
}

public function __serialize(): array
{
$properties = get_object_vars($this);
unset(
$properties['warning'],
$properties['dataMapper']
);
return $properties;
}

public function __unserialize(array $data): void
{
foreach ($data as $propertyName => $propertyValue) {
$this->{$propertyName} = $propertyValue;
}

$this->dataMapper = GeneralUtility::getContainer()->get(DataMapper::class);
}
}
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;

use ExtbaseTeam\BlogExample\Domain\Model\Administrator;
use ExtbaseTeam\BlogExample\Domain\Model\Blog;
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

class LazyLoadingProxyTest extends FunctionalTestCase
{
protected array $coreExtensionsToLoad = ['extbase'];
protected array $testExtensionsToLoad = ['typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example'];

/**
* Sets up this test suite.
*/
protected function setUp(): void
{
parent::setUp();

GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('fe_users')
->insert('fe_users', [
'uid' => 1,
'username' => 'Blog Admin',
'tx_extbase_type' => Administrator::class,
]);

$request = (new ServerRequest())->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE);
$GLOBALS['TYPO3_REQUEST'] = $request;
}

/**
* @test
*/
public function serializeAndUnserialize(): void
{
$blog = new Blog();
$blog->_setProperty('administrator', new LazyLoadingProxy($blog, 'administrator', 1));

$serialized = serialize($blog->getAdministrator());

self::assertFalse(str_contains($serialized, 'dataMapper'), 'Assert that serialized object string does not contain dataMapper');

/** @var LazyLoadingProxy $administratorProxy */
$administratorProxy = unserialize($serialized, ['allowed_classes' => true]);
self::assertInstanceOf(LazyLoadingProxy::class, $administratorProxy, 'Assert that $administratorProxy is an instance of LazyLoadingProxy');

/** @var Administrator $administrator */
$administrator = $administratorProxy->_loadRealInstance();
self::assertInstanceOf(Administrator::class, $administrator, 'Assert that $administrator is an instance of Administrator');

self::assertSame('Blog Admin', $administrator->getUsername());
}
}
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;

use ExtbaseTeam\BlogExample\Domain\Model\Blog;
use ExtbaseTeam\BlogExample\Domain\Model\Post;
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

class LazyObjectStorageTest extends FunctionalTestCase
{
protected array $coreExtensionsToLoad = ['extbase'];
protected array $testExtensionsToLoad = ['typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example'];

/**
* Sets up this test suite.
*/
protected function setUp(): void
{
parent::setUp();

$this->importCSVDataSet(__DIR__ . '/../Persistence/Fixtures/blogs.csv');
$this->importCSVDataSet(__DIR__ . '/../Persistence/Fixtures/posts.csv');

$request = (new ServerRequest())->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE);
$GLOBALS['TYPO3_REQUEST'] = $request;
}

/**
* @test
*/
public function serializeAndUnserialize(): void
{
$blog = new Blog();
$blog->_setProperty('uid', 1);
$blog->_setProperty('posts', new LazyObjectStorage($blog, 'posts', 10));

$serialized = serialize($blog->getPosts());

self::assertFalse(str_contains($serialized, 'dataMapper'), 'Assert that serialized object string does not contain dataMapper');

/** @var LazyObjectStorage $postsProxy */
$postsProxy = unserialize($serialized, ['allowed_classes' => true]);
self::assertInstanceOf(LazyObjectStorage::class, $postsProxy, 'Assert that $postsProxy is an instance of LazyObjectStorage');

/** @var Post[] $posts */
$posts = $postsProxy->toArray();

self::assertInstanceOf(Post::class, $posts[0], 'Assert that $posts[0] is an instance of Post');
self::assertInstanceOf(Post::class, $posts[1], 'Assert that $posts[1] is an instance of Post');

self::assertSame('Post1', $posts[0]->getTitle());
self::assertSame('Post2', $posts[1]->getTitle());
}
}

0 comments on commit bdba25c

Please sign in to comment.