Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3990cad

Browse files
committed
[PropertyInfo][PropertyAccess] Feature: customize behavior for property hooks on read and write
1 parent79fa5f2 commit3990cad

File tree

12 files changed

+188
-14
lines changed

12 files changed

+188
-14
lines changed

‎src/Symfony/Component/PropertyAccess/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.3
5+
---
6+
7+
* Allow to customize behavior for property hooks on read and write
8+
49
7.0
510
---
611

‎src/Symfony/Component/PropertyAccess/PropertyAccessor.php

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ class PropertyAccessor implements PropertyAccessorInterface
4747
/** @var int Allow magic __call methods */
4848
publicconstMAGIC_CALL = ReflectionExtractor::ALLOW_MAGIC_CALL;
4949

50+
publicconstBYPASS_PROPERTY_HOOK_NONE =0;
51+
publicconstBYPASS_PROPERTY_HOOK_READ =1 <<1;
52+
publicconstBYPASS_PROPERTY_HOOK_WRITE =1 <<0;
53+
5054
publicconstDO_NOT_THROW =0;
5155
publicconstTHROW_ON_INVALID_INDEX =1;
5256
publicconstTHROW_ON_INVALID_PROPERTY_PATH =2;
@@ -67,6 +71,7 @@ class PropertyAccessor implements PropertyAccessorInterface
6771
privatePropertyWriteInfoExtractorInterface$writeInfoExtractor;
6872
privatearray$readPropertyCache = [];
6973
privatearray$writePropertyCache = [];
74+
privateint$bypassPropertyHooks;
7075

7176
/**
7277
* Should not be used by application code. Use
@@ -77,19 +82,24 @@ class PropertyAccessor implements PropertyAccessorInterface
7782
* or self::DISALLOW_MAGIC_METHODS for none
7883
* @param int $throw A bitwise combination of the THROW_* constants
7984
* to specify when exceptions should be thrown
85+
* @param int $bypassPropertyHooks A bitwise combination of the BYPASS_PROPERTY_HOOK_* constants
86+
* to specify the hooks you want to bypass,
87+
* or self::BYPASS_PROPERTY_HOOK_NONE for none
8088
*/
8189
publicfunction__construct(
8290
privateint$magicMethodsFlags =self::MAGIC_GET |self::MAGIC_SET,
8391
int$throw =self::THROW_ON_INVALID_PROPERTY_PATH,
8492
?CacheItemPoolInterface$cacheItemPool =null,
8593
?PropertyReadInfoExtractorInterface$readInfoExtractor =null,
8694
?PropertyWriteInfoExtractorInterface$writeInfoExtractor =null,
95+
int$bypassPropertyHooks =self::BYPASS_PROPERTY_HOOK_NONE,
8796
) {
8897
$this->ignoreInvalidIndices =0 === ($throw &self::THROW_ON_INVALID_INDEX);
8998
$this->cacheItemPool =$cacheItemPoolinstanceof NullAdapter ?null :$cacheItemPool;// Replace the NullAdapter by the null value
9099
$this->ignoreInvalidProperty =0 === ($throw &self::THROW_ON_INVALID_PROPERTY_PATH);
91100
$this->readInfoExtractor =$readInfoExtractor ??newReflectionExtractor([],null,null,false);
92101
$this->writeInfoExtractor =$writeInfoExtractor ??newReflectionExtractor(['set'],null,null,false);
102+
$this->bypassPropertyHooks = \PHP_VERSION_ID >=80400 ?$bypassPropertyHooks :self::BYPASS_PROPERTY_HOOK_NONE;
93103
}
94104

95105
publicfunctiongetValue(object|array$objectOrArray,string|PropertyPathInterface$propertyPath):mixed
@@ -414,21 +424,30 @@ private function readProperty(array $zval, string $property, bool $ignoreInvalid
414424
throw$e;
415425
}
416426
}elseif (PropertyReadInfo::TYPE_PROPERTY ===$type) {
417-
if (!isset($object->$name) && !\array_key_exists($name, (array)$object)) {
427+
$valueSet =false;
428+
$bypassHooks =$this->bypassPropertyHooks &self::BYPASS_PROPERTY_HOOK_READ &&$access->hasHook() && !$access->isVirtual();
429+
$initialValueNotSet = !isset($object->$name) && !\array_key_exists($name, (array)$object);
430+
if ($initialValueNotSet ||$bypassHooks) {
418431
try {
419432
$r =new \ReflectionProperty($class,$name);
420-
421-
if ($r->isPublic() && !$r->hasType()) {
433+
if ($initialValueNotSet &&$r->isPublic() && !$r->hasType()) {
422434
thrownewUninitializedPropertyException(\sprintf('The property "%s::$%s" is not initialized.',$class,$name));
423435
}
436+
437+
if ($bypassHooks) {
438+
$result[self::VALUE] =$r->getRawValue($object);
439+
$valueSet =true;
440+
}
424441
}catch (\ReflectionException$e) {
425442
if (!$ignoreInvalidProperty) {
426443
thrownewNoSuchPropertyException(\sprintf('Can\'t get a way to read the property "%s" in class "%s".',$property,$class));
427444
}
428445
}
429446
}
430447

431-
$result[self::VALUE] =$object->$name;
448+
if (!$valueSet) {
449+
$result[self::VALUE] =$object->$name;
450+
}
432451

433452
if (isset($zval[self::REF]) &&$access->canBeReference()) {
434453
$result[self::REF] = &$object->$name;
@@ -531,7 +550,12 @@ private function writeProperty(array $zval, string $property, mixed $value, bool
531550
if (PropertyWriteInfo::TYPE_METHOD ===$type) {
532551
$object->{$mutator->getName()}($value);
533552
}elseif (PropertyWriteInfo::TYPE_PROPERTY ===$type) {
534-
$object->{$mutator->getName()} =$value;
553+
if ($this->bypassPropertyHooks &self::BYPASS_PROPERTY_HOOK_WRITE) {
554+
$r =new \ReflectionProperty($class,$mutator->getName());
555+
$r->setRawValue($object,$value);
556+
}else {
557+
$object->{$mutator->getName()} =$value;
558+
}
535559
}elseif (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER ===$type) {
536560
$this->writeCollection($zval,$property,$value,$mutator->getAdderInfo(),$mutator->getRemoverInfo());
537561
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespaceSymfony\Component\PropertyAccess\Tests\Fixtures;
4+
5+
class TestClassHooks
6+
{
7+
publicstring$hookGetOnly ='default' {
8+
get =>$this->hookGetOnly .' (hooked on get)';
9+
}
10+
11+
publicstring$hookSetOnly ='default' {
12+
set(string$value) {
13+
$this->hookSetOnly =$value .' (hooked on set)';
14+
}
15+
}
16+
17+
publicstring$hookBoth ='default' {
18+
get =>$this->hookBoth .' (hooked on get)';
19+
set(string$value) {
20+
$this->hookBoth =$value .' (hooked on set)';
21+
}
22+
}
23+
}

‎src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestAdderRemoverInvalidArgumentLength;
2727
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestAdderRemoverInvalidMethods;
2828
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestClass;
29+
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestClassHooks;
2930
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestClassIsWritable;
3031
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicCall;
3132
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicGet;
@@ -1030,6 +1031,45 @@ public function testIsReadableWithMissingPropertyAndLazyGhost()
10301031
$this->assertFalse($this->propertyAccessor->isReadable($lazyGhost,'dummy'));
10311032
}
10321033

1034+
/**
1035+
* @requires PHP 8.4
1036+
*/
1037+
publicfunctiontestBypassHookOnRead()
1038+
{
1039+
$instance =newTestClassHooks();
1040+
$bypassingPropertyAccessor =newPropertyAccessor(bypassPropertyHooks: PropertyAccessor::BYPASS_PROPERTY_HOOK_READ);
1041+
$this->assertSame('default',$bypassingPropertyAccessor->getValue($instance,'hookGetOnly'));
1042+
$this->assertSame('default (hooked on get)',$this->propertyAccessor->getValue($instance,'hookGetOnly'));
1043+
$this->assertSame('default',$bypassingPropertyAccessor->getValue($instance,'hookSetOnly'));
1044+
$this->assertSame('default',$this->propertyAccessor->getValue($instance,'hookSetOnly'));
1045+
$this->assertSame('default',$bypassingPropertyAccessor->getValue($instance,'hookBoth'));
1046+
$this->assertSame('default (hooked on get)',$this->propertyAccessor->getValue($instance,'hookBoth'));
1047+
}
1048+
1049+
/**
1050+
* @requires PHP 8.4
1051+
*/
1052+
publicfunctiontestBypassHookOnWrite()
1053+
{
1054+
$instance =newTestClassHooks();
1055+
$bypassingPropertyAccessor =newPropertyAccessor(bypassPropertyHooks: PropertyAccessor::BYPASS_PROPERTY_HOOK_WRITE);
1056+
$bypassingPropertyAccessor->setValue($instance,'hookGetOnly','edited');
1057+
$bypassingPropertyAccessor->setValue($instance,'hookSetOnly','edited');
1058+
$bypassingPropertyAccessor->setValue($instance,'hookBoth','edited');
1059+
1060+
$instance2 =newTestClassHooks();
1061+
$this->propertyAccessor->setValue($instance2,'hookGetOnly','edited');
1062+
$this->propertyAccessor->setValue($instance2,'hookSetOnly','edited');
1063+
$this->propertyAccessor->setValue($instance2,'hookBoth','edited');
1064+
1065+
$this->assertSame('edited (hooked on get)',$bypassingPropertyAccessor->getValue($instance,'hookGetOnly'));
1066+
$this->assertSame('edited (hooked on get)',$this->propertyAccessor->getValue($instance2,'hookGetOnly'));
1067+
$this->assertSame('edited',$bypassingPropertyAccessor->getValue($instance,'hookSetOnly'));
1068+
$this->assertSame('edited (hooked on set)',$this->propertyAccessor->getValue($instance2,'hookSetOnly'));
1069+
$this->assertSame('edited (hooked on get)',$bypassingPropertyAccessor->getValue($instance,'hookBoth'));
1070+
$this->assertSame('edited (hooked on set) (hooked on get)',$this->propertyAccessor->getValue($instance2,'hookBoth'));
1071+
}
1072+
10331073
privatefunctioncreateUninitializedObjectPropertyGhost():UninitializedObjectProperty
10341074
{
10351075
if (\PHP_VERSION_ID <80400) {

‎src/Symfony/Component/PropertyAccess/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
],
1818
"require": {
1919
"php":">=8.2",
20-
"symfony/property-info":"^6.4|^7.0"
20+
"symfony/property-info":"^7.3"
2121
},
2222
"require-dev": {
2323
"symfony/cache":"^6.4|^7.0"

‎src/Symfony/Component/PropertyInfo/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
7.3
55
---
66

7+
* Gather data from property hooks in ReflectionExtractor
78
* Add support for`non-positive-int`,`non-negative-int` and`non-zero-int` PHPStan types to`PhpStanExtractor`
89
* Add`PropertyDescriptionExtractorInterface` to`PhpStanExtractor`
910
* Deprecate the`Type` class, use`Symfony\Component\TypeInfo\Type` class from`symfony/type-info` instead

‎src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -392,12 +392,12 @@ public function getReadInfo(string $class, string $property, array $context = []
392392
returnnewPropertyReadInfo(PropertyReadInfo::TYPE_METHOD,$getsetter,$this->getReadVisibilityForMethod($method),$method->isStatic(),false);
393393
}
394394

395-
if ($allowMagicGet &&$reflClass->hasMethod('__get') && (($r =$reflClass->getMethod('__get'))->getModifiers() &$this->methodReflectionFlags)) {
396-
returnnewPropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY,$property,PropertyReadInfo::VISIBILITY_PUBLIC,false,$r->returnsReference());
395+
if ($hasProperty &&(($r =$reflClass->getProperty($property))->getModifiers() &$this->propertyReflectionFlags)) {
396+
returnnewPropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY,$property,$this->getReadVisibilityForProperty($r),$r->isStatic(),true,$this->propertyHasHook($r,'get'),$this->propertyIsVirtual($r));
397397
}
398398

399-
if ($hasProperty &&(($r =$reflClass->getProperty($property))->getModifiers() &$this->propertyReflectionFlags)) {
400-
returnnewPropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY,$property,$this->getReadVisibilityForProperty($r),$r->isStatic(),true);
399+
if ($allowMagicGet &&$reflClass->hasMethod('__get') && (($r =$reflClass->getMethod('__get'))->getModifiers() &$this->methodReflectionFlags)) {
400+
returnnewPropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY,$property,PropertyReadInfo::VISIBILITY_PUBLIC,false,$r->returnsReference(),false,false);
401401
}
402402

403403
if ($allowMagicCall &&$reflClass->hasMethod('__call') && ($reflClass->getMethod('__call')->getModifiers() &$this->methodReflectionFlags)) {
@@ -481,7 +481,7 @@ public function getWriteInfo(string $class, string $property, array $context = [
481481
if ($reflClass->hasProperty($property) && ($reflClass->getProperty($property)->getModifiers() &$this->propertyReflectionFlags)) {
482482
$reflProperty =$reflClass->getProperty($property);
483483
if (!$reflProperty->isReadOnly()) {
484-
returnnewPropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY,$property,$this->getWriteVisibilityForProperty($reflProperty),$reflProperty->isStatic());
484+
returnnewPropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY,$property,$this->getWriteVisibilityForProperty($reflProperty),$reflProperty->isStatic(),$this->propertyHasHook($reflProperty,'set'));
485485
}
486486

487487
$errors[] = [\sprintf('The property "%s" in class "%s" is a promoted readonly property.',$property,$reflClass->getName())];
@@ -491,7 +491,7 @@ public function getWriteInfo(string $class, string $property, array $context = [
491491
if ($allowMagicSet) {
492492
[$accessible,$methodAccessibleErrors] =$this->isMethodAccessible($reflClass,'__set',2);
493493
if ($accessible) {
494-
returnnewPropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY,$property, PropertyWriteInfo::VISIBILITY_PUBLIC,false);
494+
returnnewPropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY,$property, PropertyWriteInfo::VISIBILITY_PUBLIC,false,false);
495495
}
496496

497497
$errors[] =$methodAccessibleErrors;
@@ -894,6 +894,16 @@ private function isMethodAccessible(\ReflectionClass $class, string $methodName,
894894
return [false,$errors];
895895
}
896896

897+
privatefunctionpropertyHasHook(\ReflectionProperty$property,string$hookType):bool
898+
{
899+
return \PHP_VERSION_ID >=80400 &&$property->hasHook(\PropertyHookType::from($hookType));
900+
}
901+
902+
privatefunctionpropertyIsVirtual(\ReflectionProperty$property):bool
903+
{
904+
return \PHP_VERSION_ID >=80400 &&$property->isVirtual();
905+
}
906+
897907
/**
898908
* Camelizes a given string.
899909
*/

‎src/Symfony/Component/PropertyInfo/PropertyReadInfo.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public function __construct(
3131
privatereadonlystring$visibility,
3232
privatereadonlybool$static,
3333
privatereadonlybool$byRef,
34+
privatereadonly ?bool$hasHook =null,
35+
privatereadonly ?bool$isVirtual =null,
3436
) {
3537
}
3638

@@ -67,4 +69,14 @@ public function canBeReference(): bool
6769
{
6870
return$this->byRef;
6971
}
72+
73+
publicfunctionhasHook(): ?bool
74+
{
75+
return$this->hasHook;
76+
}
77+
78+
publicfunctionisVirtual(): ?bool
79+
{
80+
return$this->isVirtual;
81+
}
7082
}

‎src/Symfony/Component/PropertyInfo/PropertyWriteInfo.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public function __construct(
3737
privatereadonly ?string$name =null,
3838
privatereadonly ?string$visibility =null,
3939
privatereadonly ?bool$static =null,
40+
privatereadonly ?bool$hasHook =null,
4041
) {
4142
}
4243

@@ -114,4 +115,9 @@ public function hasErrors(): bool
114115
{
115116
return (bool)\count($this->errors);
116117
}
118+
119+
publicfunctionhasHook(): ?bool
120+
{
121+
return$this->hasHook;
122+
}
117123
}

‎src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
useSymfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy;
2222
useSymfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue;
2323
useSymfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
24+
useSymfony\Component\PropertyInfo\Tests\Fixtures\HookedProperties;
2425
useSymfony\Component\PropertyInfo\Tests\Fixtures\NotInstantiable;
2526
useSymfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy;
2627
useSymfony\Component\PropertyInfo\Tests\Fixtures\Php71Dummy;
@@ -788,8 +789,14 @@ public function testAsymmetricVisibilityAllowPrivateOnly()
788789
publicfunctiontestVirtualProperties()
789790
{
790791
$this->assertTrue($this->extractor->isReadable(VirtualProperties::class,'virtualNoSetHook'));
792+
$this->assertTrue($this->extractor->getReadInfo(VirtualProperties::class,'virtualNoSetHook')->isVirtual());
793+
$this->assertTrue($this->extractor->getReadInfo(VirtualProperties::class,'virtualNoSetHook')->hasHook());
791794
$this->assertTrue($this->extractor->isReadable(VirtualProperties::class,'virtualSetHookOnly'));
795+
$this->assertTrue($this->extractor->getReadInfo(VirtualProperties::class,'virtualSetHookOnly')->isVirtual());
796+
$this->assertFalse($this->extractor->getReadInfo(VirtualProperties::class,'virtualSetHookOnly')->hasHook());
792797
$this->assertTrue($this->extractor->isReadable(VirtualProperties::class,'virtualHook'));
798+
$this->assertTrue($this->extractor->getReadInfo(VirtualProperties::class,'virtualHook')->isVirtual());
799+
$this->assertTrue($this->extractor->getReadInfo(VirtualProperties::class,'virtualHook')->hasHook());
793800
$this->assertFalse($this->extractor->isWritable(VirtualProperties::class,'virtualNoSetHook'));
794801
$this->assertTrue($this->extractor->isWritable(VirtualProperties::class,'virtualSetHookOnly'));
795802
$this->assertTrue($this->extractor->isWritable(VirtualProperties::class,'virtualHook'));
@@ -814,6 +821,22 @@ public function testAsymmetricVisibilityMutator(string $property, string $readVi
814821
$this->assertSame($writeVisibility,$writeMutator->getVisibility());
815822
}
816823

824+
/**
825+
* @requires PHP 8.4
826+
*/
827+
publicfunctiontestHookedProperties()
828+
{
829+
$this->assertTrue($this->extractor->getReadInfo(HookedProperties::class,'hookGetOnly')->hasHook());
830+
$this->assertFalse($this->extractor->getReadInfo(HookedProperties::class,'hookGetOnly')->isVirtual());
831+
$this->assertFalse($this->extractor->getWriteInfo(HookedProperties::class,'hookGetOnly')->hasHook());
832+
$this->assertFalse($this->extractor->getReadInfo(HookedProperties::class,'hookSetOnly')->hasHook());
833+
$this->assertFalse($this->extractor->getReadInfo(HookedProperties::class,'hookSetOnly')->isVirtual());
834+
$this->assertTrue($this->extractor->getWriteInfo(HookedProperties::class,'hookSetOnly')->hasHook());
835+
$this->assertTrue($this->extractor->getReadInfo(HookedProperties::class,'hookBoth')->hasHook());
836+
$this->assertFalse($this->extractor->getReadInfo(HookedProperties::class,'hookBoth')->isVirtual());
837+
$this->assertTrue($this->extractor->getWriteInfo(HookedProperties::class,'hookBoth')->hasHook());
838+
}
839+
817840
publicstaticfunctionprovideAsymmetricVisibilityMutator():iterable
818841
{
819842
yield ['publicPrivate', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PRIVATE];
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespaceSymfony\Component\PropertyInfo\Tests\Fixtures;
13+
14+
class HookedProperties
15+
{
16+
publicstring$hookGetOnly {
17+
get =>$this->hookGetOnly .' (hooked on get)';
18+
}
19+
publicstring$hookSetOnly {
20+
set(string$value) {
21+
$this->hookSetOnly =$value .' (hooked on set)';
22+
}
23+
}
24+
publicstring$hookBoth {
25+
get =>$this->hookBoth .' (hooked on get)';
26+
set(string$value) {
27+
$this->hookBoth =$value .' (hooked on set)';
28+
}
29+
}
30+
}

‎src/Symfony/Component/PropertyInfo/Tests/Fixtures/VirtualProperties.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@
1414
class VirtualProperties
1515
{
1616
publicbool$virtualNoSetHook { get =>true; }
17-
publicbool$virtualSetHookOnly { set=>$value; }
18-
publicbool$virtualHook { get =>true; set=>$value; }
17+
publicbool$virtualSetHookOnly { set(bool$value) { } }
18+
publicbool$virtualHook { get =>true; set(bool$value) { } }
1919
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp