Skip to content

Commit faa1515

Browse files
michaljusiegasebastianbergmann
authored andcommitted
Implement logic to blocks readonly classes to be doubled.
1 parent 5c6e811 commit faa1515

File tree

5 files changed

+70
-0
lines changed

5 files changed

+70
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* This file is part of PHPUnit.
4+
*
5+
* (c) Sebastian Bergmann <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
namespace PHPUnit\Framework\MockObject;
11+
12+
use function sprintf;
13+
14+
/**
15+
* @internal This class is not covered by the backward compatibility promise for PHPUnit
16+
*/
17+
final class ClassIsReadonlyException extends \PHPUnit\Framework\Exception implements Exception
18+
{
19+
public function __construct(string $className)
20+
{
21+
parent::__construct(
22+
sprintf(
23+
'Class "%s" is declared "readonly" and cannot be doubled',
24+
$className
25+
)
26+
);
27+
}
28+
}

src/Framework/MockObject/Generator.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use function is_array;
2828
use function is_object;
2929
use function md5;
30+
use function method_exists;
3031
use function mt_rand;
3132
use function preg_match;
3233
use function preg_match_all;
@@ -146,6 +147,7 @@ public function __clone()
146147
* @throws \PHPUnit\Framework\InvalidArgumentException
147148
* @throws ClassAlreadyExistsException
148149
* @throws ClassIsFinalException
150+
* @throws ClassIsReadonlyException
149151
* @throws DuplicateMethodException
150152
* @throws InvalidMethodNameException
151153
* @throws OriginalConstructorInvocationRequiredException
@@ -299,6 +301,7 @@ public function getMockForInterfaces(array $interfaces, bool $callAutoload = tru
299301
* @throws \PHPUnit\Framework\InvalidArgumentException
300302
* @throws ClassAlreadyExistsException
301303
* @throws ClassIsFinalException
304+
* @throws ClassIsReadonlyException
302305
* @throws DuplicateMethodException
303306
* @throws InvalidMethodNameException
304307
* @throws OriginalConstructorInvocationRequiredException
@@ -360,6 +363,7 @@ interface_exists($originalClassName, $callAutoload)) {
360363
* @throws \PHPUnit\Framework\InvalidArgumentException
361364
* @throws ClassAlreadyExistsException
362365
* @throws ClassIsFinalException
366+
* @throws ClassIsReadonlyException
363367
* @throws DuplicateMethodException
364368
* @throws InvalidMethodNameException
365369
* @throws OriginalConstructorInvocationRequiredException
@@ -442,6 +446,7 @@ public function getObjectForTrait(string $traitName, string $traitClassName = ''
442446

443447
/**
444448
* @throws ClassIsFinalException
449+
* @throws ClassIsReadonlyException
445450
* @throws ReflectionException
446451
* @throws RuntimeException
447452
*/
@@ -764,6 +769,7 @@ private function getObject(MockType $mockClass, $type = '', bool $callOriginalCo
764769

765770
/**
766771
* @throws ClassIsFinalException
772+
* @throws ClassIsReadonlyException
767773
* @throws ReflectionException
768774
* @throws RuntimeException
769775
*/
@@ -819,6 +825,10 @@ private function generateMock(string $type, ?array $explicitMethods, string $moc
819825
throw new ClassIsFinalException($_mockClassName['fullClassName']);
820826
}
821827

828+
if (method_exists($class, 'isReadOnly') && $class->isReadOnly()) {
829+
throw new ClassIsReadonlyException($_mockClassName['fullClassName']);
830+
}
831+
822832
// @see https://github.com/sebastianbergmann/phpunit/issues/2995
823833
if ($isInterface && $class->implementsInterface(Throwable::class)) {
824834
$actualClassName = Exception::class;

src/Framework/MockObject/MockBuilder.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ public function __construct(TestCase $testCase, $type)
114114
* @throws \PHPUnit\Framework\InvalidArgumentException
115115
* @throws ClassAlreadyExistsException
116116
* @throws ClassIsFinalException
117+
* @throws ClassIsReadonlyException
117118
* @throws DuplicateMethodException
118119
* @throws InvalidMethodNameException
119120
* @throws OriginalConstructorInvocationRequiredException
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* This file is part of PHPUnit.
4+
*
5+
* (c) Sebastian Bergmann <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
namespace PHPUnit\TestFixture\MockObject;
11+
12+
readonly class ReadonlyClass
13+
{
14+
public function __construct(private mixed $value)
15+
{
16+
}
17+
18+
public function value(): mixed
19+
{
20+
return $this->value;
21+
}
22+
}

tests/unit/Framework/MockObject/GeneratorTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use PHPUnit\TestFixture\FinalClass;
2626
use PHPUnit\TestFixture\InterfaceWithSemiReservedMethodName;
2727
use PHPUnit\TestFixture\MockObject\AbstractMockTestClass;
28+
use PHPUnit\TestFixture\MockObject\ReadonlyClass;
2829
use PHPUnit\TestFixture\SingletonClass;
2930
use RuntimeException;
3031
use stdClass;
@@ -325,6 +326,14 @@ public function testCannotMockFinalClass(): void
325326
$this->createMock(FinalClass::class);
326327
}
327328

329+
public function testCannotMockReadonlyClass(): void
330+
{
331+
$this->expectException(ClassIsReadonlyException::class);
332+
333+
/* @noinspection ClassMockingCorrectnessInspection */
334+
$this->createMock(ReadonlyClass::class);
335+
}
336+
328337
public function testCanDoubleIntersectionOfMultipleInterfaces(): void
329338
{
330339
$stub = $this->generator->getMockForInterfaces(

0 commit comments

Comments
 (0)