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

Commita396856

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

File tree

11 files changed

+194
-18
lines changed

11 files changed

+194
-18
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: 37 additions & 11 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+
publicconstDO_NOT_BYPASS_HOOKS_ON_PROPERTY =0;
51+
publicconstBYPASS_HOOKS_ON_PROPERTY_READ =1 <<1;
52+
publicconstBYPASS_HOOKS_ON_PROPERTY_WRITE =1 <<0;
53+
5054
publicconstDO_NOT_THROW =0;
5155
publicconstTHROW_ON_INVALID_INDEX =1;
5256
publicconstTHROW_ON_INVALID_PROPERTY_PATH =2;
@@ -67,29 +71,35 @@ 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
7378
* {@link PropertyAccess::createPropertyAccessor()} instead.
7479
*
75-
* @param int $magicMethods A bitwise combination of the MAGIC_* constants
76-
* to specify the allowed magic methods (__get, __set, __call)
77-
* or self::DISALLOW_MAGIC_METHODS for none
78-
* @param int $throw A bitwise combination of the THROW_* constants
79-
* to specify when exceptions should be thrown
80+
* @param int $magicMethodsFlags A bitwise combination of the MAGIC_* constants
81+
* to specify the allowed magic methods (__get, __set, __call)
82+
* or self::DISALLOW_MAGIC_METHODS for none
83+
* @param int $throw A bitwise combination of the THROW_* constants
84+
* to specify when exceptions should be thrown
85+
* @param int $byPassPropertyHooks A bitwise combination of the BYPASS_HOOKS_* constants
86+
* to specify the hooks you want to bypass,
87+
* or self::DO_NOT_BYPASS_HOOKS_ON_PROPERTY 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::DO_NOT_BYPASS_HOOKS_ON_PROPERTY,
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::DO_NOT_BYPASS_HOOKS_ON_PROPERTY;
93103
}
94104

95105
publicfunctiongetValue(object|array$objectOrArray,string|PropertyPathInterface$propertyPath):mixed
@@ -216,7 +226,7 @@ public function isReadable(object|array $objectOrArray, string|PropertyPathInter
216226
];
217227

218228
// handle stdClass with properties with a dot in the name
219-
if ($objectOrArrayinstanceof \stdClass &&str_contains($propertyPath,'.')&&property_exists($objectOrArray,$propertyPath)) {
229+
if ($objectOrArrayinstanceof \stdClass &&str_contains($propertyPath,'.') &&property_exists($objectOrArray,$propertyPath)) {
220230
$this->readProperty($zval,$propertyPath,$this->ignoreInvalidProperty);
221231
}else {
222232
$this->readPropertiesUntil($zval,$propertyPath,$propertyPath->getLength(),$this->ignoreInvalidIndices);
@@ -414,12 +424,21 @@ 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+
$useBypass =$this->byPassPropertyHooks &self::BYPASS_HOOKS_ON_PROPERTY_READ &&$access->hasHook() && !$access->isVirtual();
429+
$valueSeemsToBeNotSet = !isset($object->$name) && !\array_key_exists($name, (array)$object);
430+
if ($valueSeemsToBeNotSet ||$useBypass) {
418431
try {
419432
$r =new \ReflectionProperty($class,$name);
433+
if ($valueSeemsToBeNotSet) {
434+
if ($r->isPublic() && !$r->hasType()) {
435+
thrownewUninitializedPropertyException(\sprintf('The property "%s::$%s" is not initialized.',$class,$name));
436+
}
437+
}
420438

421-
if ($r->isPublic() && !$r->hasType()) {
422-
thrownewUninitializedPropertyException(\sprintf('The property "%s::$%s" is not initialized.',$class,$name));
439+
if ($useBypass) {
440+
$result[self::VALUE] =$r->getRawValue($object);
441+
$valueSet =true;
423442
}
424443
}catch (\ReflectionException$e) {
425444
if (!$ignoreInvalidProperty) {
@@ -428,7 +447,9 @@ private function readProperty(array $zval, string $property, bool $ignoreInvalid
428447
}
429448
}
430449

431-
$result[self::VALUE] =$object->$name;
450+
if (!$valueSet) {
451+
$result[self::VALUE] =$object->$name;
452+
}
432453

433454
if (isset($zval[self::REF]) &&$access->canBeReference()) {
434455
$result[self::REF] = &$object->$name;
@@ -531,7 +552,12 @@ private function writeProperty(array $zval, string $property, mixed $value, bool
531552
if (PropertyWriteInfo::TYPE_METHOD ===$type) {
532553
$object->{$mutator->getName()}($value);
533554
}elseif (PropertyWriteInfo::TYPE_PROPERTY ===$type) {
534-
$object->{$mutator->getName()} =$value;
555+
if ($this->byPassPropertyHooks &self::BYPASS_HOOKS_ON_PROPERTY_WRITE) {
556+
$r =new \ReflectionProperty($class,$mutator->getName());
557+
$r->setRawValue($object,$value);
558+
}else {
559+
$object->{$mutator->getName()} =$value;
560+
}
535561
}elseif (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER ===$type) {
536562
$this->writeCollection($zval,$property,$value,$mutator->getAdderInfo(),$mutator->getRemoverInfo());
537563
}
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
@@ -25,6 +25,7 @@
2525
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestAdderRemoverInvalidArgumentLength;
2626
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestAdderRemoverInvalidMethods;
2727
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestClass;
28+
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestClassHooks;
2829
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestClassIsWritable;
2930
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicCall;
3031
useSymfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicGet;
@@ -1029,6 +1030,45 @@ public function testIsReadableWithMissingPropertyAndLazyGhost()
10291030
$this->assertFalse($this->propertyAccessor->isReadable($lazyGhost,'dummy'));
10301031
}
10311032

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

‎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

910
7.1

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -384,11 +384,11 @@ public function getReadInfo(string $class, string $property, array $context = []
384384
}
385385

386386
if ($allowMagicGet &&$reflClass->hasMethod('__get') && (($r =$reflClass->getMethod('__get'))->getModifiers() &$this->methodReflectionFlags)) {
387-
returnnewPropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY,$property, PropertyReadInfo::VISIBILITY_PUBLIC,false,$r->returnsReference());
387+
returnnewPropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY,$property, PropertyReadInfo::VISIBILITY_PUBLIC,false,$r->returnsReference(),false,false);
388388
}
389389

390390
if ($hasProperty && (($r =$reflClass->getProperty($property))->getModifiers() &$this->propertyReflectionFlags)) {
391-
returnnewPropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY,$property,$this->getReadVisiblityForProperty($r),$r->isStatic(),true);
391+
returnnewPropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY,$property,$this->getReadVisiblityForProperty($r),$r->isStatic(),true,$this->propertyHasHook($r,'get'),$this->propertyIsVirtual($r));
392392
}
393393

394394
if ($allowMagicCall &&$reflClass->hasMethod('__call') && ($reflClass->getMethod('__call')->getModifiers() &$this->methodReflectionFlags)) {
@@ -472,7 +472,7 @@ public function getWriteInfo(string $class, string $property, array $context = [
472472
if ($reflClass->hasProperty($property) && ($reflClass->getProperty($property)->getModifiers() &$this->propertyReflectionFlags)) {
473473
$reflProperty =$reflClass->getProperty($property);
474474
if (!$reflProperty->isReadOnly()) {
475-
returnnewPropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY,$property,$this->getWriteVisiblityForProperty($reflProperty),$reflProperty->isStatic());
475+
returnnewPropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY,$property,$this->getWriteVisiblityForProperty($reflProperty),$reflProperty->isStatic(),$this->propertyHasHook($reflProperty,'set'));
476476
}
477477

478478
$errors[] = [\sprintf('The property "%s" in class "%s" is a promoted readonly property.',$property,$reflClass->getName())];
@@ -482,7 +482,7 @@ public function getWriteInfo(string $class, string $property, array $context = [
482482
if ($allowMagicSet) {
483483
[$accessible,$methodAccessibleErrors] =$this->isMethodAccessible($reflClass,'__set',2);
484484
if ($accessible) {
485-
returnnewPropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY,$property, PropertyWriteInfo::VISIBILITY_PUBLIC,false);
485+
returnnewPropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY,$property, PropertyWriteInfo::VISIBILITY_PUBLIC,false,false);
486486
}
487487

488488
$errors[] =$methodAccessibleErrors;
@@ -491,7 +491,7 @@ public function getWriteInfo(string $class, string $property, array $context = [
491491
if ($allowMagicCall) {
492492
[$accessible,$methodAccessibleErrors] =$this->isMethodAccessible($reflClass,'__call',2);
493493
if ($accessible) {
494-
returnnewPropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD,'set'.$camelized, PropertyWriteInfo::VISIBILITY_PUBLIC,false);
494+
returnnewPropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD,'set'.$camelized, PropertyWriteInfo::VISIBILITY_PUBLIC,false,false);
495495
}
496496

497497
$errors[] =$methodAccessibleErrors;
@@ -885,6 +885,16 @@ private function isMethodAccessible(\ReflectionClass $class, string $methodName,
885885
return [false,$errors];
886886
}
887887

888+
privatefunctionpropertyHasHook(\ReflectionProperty$property,string$hookType):bool
889+
{
890+
return \PHP_VERSION_ID >=80400 &&$property->hasHook(\PropertyHookType::from($hookType));
891+
}
892+
893+
privatefunctionpropertyIsVirtual(\ReflectionProperty$property):bool
894+
{
895+
return \PHP_VERSION_ID >=80400 &&$property->isVirtual();
896+
}
897+
888898
/**
889899
* Camelizes a given string.
890900
*/

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public function __construct(
3333
privatereadonlystring$visibility,
3434
privatereadonlybool$static,
3535
privatereadonlybool$byRef,
36+
privatereadonly ?bool$hasHook =null,
37+
privatereadonly ?bool$isVirtual =null,
3638
) {
3739
}
3840

@@ -69,4 +71,14 @@ public function canBeReference(): bool
6971
{
7072
return$this->byRef;
7173
}
74+
75+
publicfunctionhasHook(): ?bool
76+
{
77+
return$this->hasHook;
78+
}
79+
80+
publicfunctionisVirtual(): ?bool
81+
{
82+
return$this->isVirtual;
83+
}
7284
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public function __construct(
3939
privatereadonly ?string$name =null,
4040
privatereadonly ?string$visibility =null,
4141
privatereadonly ?bool$static =null,
42+
privatereadonly ?bool$hasHook =null,
4243
) {
4344
}
4445

@@ -116,4 +117,9 @@ public function hasErrors(): bool
116117
{
117118
return (bool)\count($this->errors);
118119
}
120+
121+
publicfunctionhasHook(): ?bool
122+
{
123+
return$this->hasHook;
124+
}
119125
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
useSymfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy;
2121
useSymfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue;
2222
useSymfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
23+
useSymfony\Component\PropertyInfo\Tests\Fixtures\HookedProperties;
2324
useSymfony\Component\PropertyInfo\Tests\Fixtures\NotInstantiable;
2425
useSymfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy;
2526
useSymfony\Component\PropertyInfo\Tests\Fixtures\Php71Dummy;
@@ -754,8 +755,14 @@ public function testAsymmetricVisibilityAllowPrivateOnly()
754755
publicfunctiontestVirtualProperties()
755756
{
756757
$this->assertTrue($this->extractor->isReadable(VirtualProperties::class,'virtualNoSetHook'));
758+
$this->assertTrue($this->extractor->getReadInfo(VirtualProperties::class,'virtualNoSetHook')->isVirtual());
759+
$this->assertTrue($this->extractor->getReadInfo(VirtualProperties::class,'virtualNoSetHook')->hasHook());
757760
$this->assertTrue($this->extractor->isReadable(VirtualProperties::class,'virtualSetHookOnly'));
761+
$this->assertTrue($this->extractor->getReadInfo(VirtualProperties::class,'virtualSetHookOnly')->isVirtual());
762+
$this->assertFalse($this->extractor->getReadInfo(VirtualProperties::class,'virtualSetHookOnly')->hasHook());
758763
$this->assertTrue($this->extractor->isReadable(VirtualProperties::class,'virtualHook'));
764+
$this->assertTrue($this->extractor->getReadInfo(VirtualProperties::class,'virtualHook')->isVirtual());
765+
$this->assertTrue($this->extractor->getReadInfo(VirtualProperties::class,'virtualHook')->hasHook());
759766
$this->assertFalse($this->extractor->isWritable(VirtualProperties::class,'virtualNoSetHook'));
760767
$this->assertTrue($this->extractor->isWritable(VirtualProperties::class,'virtualSetHookOnly'));
761768
$this->assertTrue($this->extractor->isWritable(VirtualProperties::class,'virtualHook'));
@@ -780,6 +787,22 @@ public function testAsymmetricVisibilityMutator(string $property, string $readVi
780787
$this->assertSame($writeVisibility,$writeMutator->getVisibility());
781788
}
782789

790+
/**
791+
* @requires PHP 8.4
792+
*/
793+
publicfunctiontestHookedProperties()
794+
{
795+
$this->assertTrue($this->extractor->getReadInfo(HookedProperties::class,'hookGetOnly')->hasHook());
796+
$this->assertFalse($this->extractor->getReadInfo(HookedProperties::class,'hookGetOnly')->isVirtual());
797+
$this->assertFalse($this->extractor->getWriteInfo(HookedProperties::class,'hookGetOnly')->hasHook());
798+
$this->assertFalse($this->extractor->getReadInfo(HookedProperties::class,'hookSetOnly')->hasHook());
799+
$this->assertFalse($this->extractor->getReadInfo(HookedProperties::class,'hookSetOnly')->isVirtual());
800+
$this->assertTrue($this->extractor->getWriteInfo(HookedProperties::class,'hookSetOnly')->hasHook());
801+
$this->assertTrue($this->extractor->getReadInfo(HookedProperties::class,'hookBoth')->hasHook());
802+
$this->assertFalse($this->extractor->getReadInfo(HookedProperties::class,'hookBoth')->isVirtual());
803+
$this->assertTrue($this->extractor->getWriteInfo(HookedProperties::class,'hookBoth')->hasHook());
804+
}
805+
783806
publicstaticfunctionprovideAsymmetricVisibilityMutator():iterable
784807
{
785808
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+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp