Skip to content

Commit c04239a

Browse files
authored
Fix access to parent properties in matchers (#88)
Backport of #72.
1 parent 770e509 commit c04239a

File tree

11 files changed

+132
-11
lines changed

11 files changed

+132
-11
lines changed

fixtures/f008/A.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace DeepCopy\f008;
4+
5+
class A
6+
{
7+
private $foo;
8+
9+
public function __construct($foo)
10+
{
11+
$this->foo = $foo;
12+
}
13+
14+
public function getFoo()
15+
{
16+
return $this->foo;
17+
}
18+
}

fixtures/f008/B.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace DeepCopy\f008;
4+
5+
class B extends A
6+
{
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace DeepCopy\Exception;
4+
5+
use ReflectionException;
6+
7+
class PropertyException extends ReflectionException
8+
{
9+
}

src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace DeepCopy\Filter\Doctrine;
44

55
use DeepCopy\Filter\Filter;
6-
use ReflectionProperty;
6+
use DeepCopy\Reflection\ReflectionHelper;
77

88
/**
99
* @final
@@ -17,7 +17,7 @@ class DoctrineCollectionFilter implements Filter
1717
*/
1818
public function apply($object, $property, $objectCopier)
1919
{
20-
$reflectionProperty = new ReflectionProperty($object, $property);
20+
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
2121

2222
$reflectionProperty->setAccessible(true);
2323
$oldCollection = $reflectionProperty->getValue($object);

src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
namespace DeepCopy\Filter\Doctrine;
44

55
use DeepCopy\Filter\Filter;
6+
use DeepCopy\Reflection\ReflectionHelper;
67
use Doctrine\Common\Collections\ArrayCollection;
7-
use ReflectionProperty;
88

99
/**
1010
* @final
@@ -20,7 +20,7 @@ class DoctrineEmptyCollectionFilter implements Filter
2020
*/
2121
public function apply($object, $property, $objectCopier)
2222
{
23-
$reflectionProperty = new ReflectionProperty($object, $property);
23+
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
2424
$reflectionProperty->setAccessible(true);
2525

2626
$reflectionProperty->setValue($object, new ArrayCollection());

src/DeepCopy/Filter/ReplaceFilter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace DeepCopy\Filter;
44

5-
use ReflectionProperty;
5+
use DeepCopy\Reflection\ReflectionHelper;
66

77
/**
88
* @final
@@ -29,7 +29,7 @@ public function __construct(callable $callable)
2929
*/
3030
public function apply($object, $property, $objectCopier)
3131
{
32-
$reflectionProperty = new ReflectionProperty($object, $property);
32+
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
3333
$reflectionProperty->setAccessible(true);
3434

3535
$value = call_user_func($this->callback, $reflectionProperty->getValue($object));

src/DeepCopy/Filter/SetNullFilter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace DeepCopy\Filter;
44

5-
use ReflectionProperty;
5+
use DeepCopy\Reflection\ReflectionHelper;
66

77
/**
88
* @final
@@ -16,7 +16,7 @@ class SetNullFilter implements Filter
1616
*/
1717
public function apply($object, $property, $objectCopier)
1818
{
19-
$reflectionProperty = new ReflectionProperty($object, $property);
19+
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
2020

2121
$reflectionProperty->setAccessible(true);
2222
$reflectionProperty->setValue($object, null);

src/DeepCopy/Matcher/PropertyTypeMatcher.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace DeepCopy\Matcher;
44

5+
use DeepCopy\Reflection\ReflectionHelper;
56
use ReflectionException;
6-
use ReflectionProperty;
77

88
/**
99
* Matches a property by its type.
@@ -34,7 +34,7 @@ public function __construct($propertyType)
3434
public function matches($object, $property)
3535
{
3636
try {
37-
$reflectionProperty = new ReflectionProperty($object, $property);
37+
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
3838
} catch (ReflectionException $exception) {
3939
return false;
4040
}

src/DeepCopy/Reflection/ReflectionHelper.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
namespace DeepCopy\Reflection;
44

5+
use DeepCopy\Exception\PropertyException;
56
use ReflectionClass;
7+
use ReflectionException;
8+
use ReflectionObject;
69
use ReflectionProperty;
710

811
class ReflectionHelper
@@ -40,4 +43,36 @@ public static function getProperties(ReflectionClass $ref)
4043

4144
return $propsArr;
4245
}
46+
47+
/**
48+
* Retrieves property by name from object and all its ancestors.
49+
*
50+
* @param object|string $object
51+
* @param string $name
52+
*
53+
* @throws PropertyException
54+
* @throws ReflectionException
55+
*
56+
* @return ReflectionProperty
57+
*/
58+
public static function getProperty($object, $name)
59+
{
60+
$reflection = is_object($object) ? new ReflectionObject($object) : new ReflectionClass($object);
61+
62+
if ($reflection->hasProperty($name)) {
63+
return $reflection->getProperty($name);
64+
}
65+
66+
if ($parentClass = $reflection->getParentClass()) {
67+
return self::getProperty($parentClass->getName(), $name);
68+
}
69+
70+
throw new PropertyException(
71+
sprintf(
72+
'The class "%s" doesn\'t have a property with the given name: "%s".',
73+
is_object($object) ? get_class($object) : $object,
74+
$name
75+
)
76+
);
77+
}
4378
}

tests/DeepCopyTest/DeepCopyTest.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
use DeepCopy\f004;
1414
use DeepCopy\f005;
1515
use DeepCopy\f006;
16+
use DeepCopy\f008;
1617
use DeepCopy\Filter\KeepFilter;
1718
use DeepCopy\Filter\SetNullFilter;
1819
use DeepCopy\Matcher\PropertyNameMatcher;
20+
use DeepCopy\Matcher\PropertyTypeMatcher;
1921
use DeepCopy\TypeFilter\ShallowCopyFilter;
2022
use DeepCopy\TypeMatcher\TypeMatcher;
2123
use PHPUnit_Framework_TestCase;
@@ -118,7 +120,7 @@ public function test_it_can_copy_an_object_with_an_object_property()
118120
$this->assertEqualButNotSame($foo->bar, $copy->bar);
119121
}
120122

121-
public function test_dynamic_properties_are_copied()
123+
public function test_it_copies_dynamic_properties()
122124
{
123125
$foo = new stdClass();
124126
$bar = new stdClass();
@@ -375,6 +377,22 @@ public function test_it_can_copy_a_SplDoublyLinkedList()
375377
$this->assertEqualButNotSame($b, $aCopy->b);
376378
}
377379

380+
/**
381+
* @ticket https://github.com/myclabs/DeepCopy/issues/62
382+
*/
383+
public function test_matchers_can_access_to_parent_private_properties()
384+
{
385+
$deepCopy = new DeepCopy();
386+
$deepCopy->addFilter(new SetNullFilter(), new PropertyTypeMatcher(stdClass::class));
387+
388+
$object = new f008\B(new stdClass());
389+
390+
/** @var f008\B $copy */
391+
$copy = $deepCopy->copy($object);
392+
393+
$this->assertNull($copy->getFoo());
394+
}
395+
378396
private function assertEqualButNotSame($expected, $val)
379397
{
380398
$this->assertEquals($expected, $val);

tests/DeepCopyTest/Reflection/ReflectionHelperTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use DeepCopy\Reflection\ReflectionHelper;
66
use PHPUnit_Framework_TestCase;
77
use ReflectionClass;
8+
use ReflectionProperty;
89

910
/**
1011
* @covers \DeepCopy\Reflection\ReflectionHelper
@@ -35,6 +36,39 @@ public function test_it_retrieves_the_object_properties()
3536

3637
$this->assertSame($expectedProps, array_keys($actualProps));
3738
}
39+
40+
/**
41+
* @dataProvider provideProperties
42+
*/
43+
public function test_it_can_retrieve_an_object_property($name)
44+
{
45+
$object = new ReflectionHelperTestChild();
46+
47+
$property = ReflectionHelper::getProperty($object, $name);
48+
49+
$this->assertInstanceOf(ReflectionProperty::class, $property);
50+
51+
$this->assertSame($name, $property->getName());
52+
}
53+
54+
public function provideProperties()
55+
{
56+
return [
57+
'public property' => ['a10'],
58+
'private property' => ['a102'],
59+
'private property of ancestor' => ['a3']
60+
];
61+
}
62+
63+
/**
64+
* @expectedException \DeepCopy\Exception\PropertyException
65+
*/
66+
public function test_it_cannot_retrieve_a_non_existent_prperty()
67+
{
68+
$object = new ReflectionHelperTestChild();
69+
70+
ReflectionHelper::getProperty($object, 'non existent property');
71+
}
3872
}
3973

4074
class ReflectionHelperTestParent

0 commit comments

Comments
 (0)