Skip to content

Commit 71cf771

Browse files
committed
feat(property-inspector): Implement PropertyInspector component
- Add AttributeAnalyzer for analyzing object properties and attributes - Create PropertyInspector for inspecting and processing object attributes - Implement exception handling with custom PropertyInspectionException - Define contracts for AttributeAnalyzer, PropertyAttributeHandler, and PropertyInspector - Add comprehensive unit tests for AttributeAnalyzer and PropertyInspector - Include application.php for demonstrating component usage - Set up directory structure and autoloading
1 parent ba620cc commit 71cf771

10 files changed

+462
-5117
lines changed

composer.lock

Lines changed: 0 additions & 5117 deletions
This file was deleted.

src/AttributeAnalyzer.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\PropertyInspector;
6+
7+
use KaririCode\PropertyInspector\Contract\AttributeAnalyzer as AttributeAnalyzerContract;
8+
use KaririCode\PropertyInspector\Exception\PropertyInspectionException;
9+
10+
final class AttributeAnalyzer implements AttributeAnalyzerContract
11+
{
12+
public function __construct(private readonly string $attributeClass)
13+
{
14+
}
15+
16+
public function analyzeObject(object $object): array
17+
{
18+
try {
19+
$results = [];
20+
$reflection = new \ReflectionClass($object);
21+
22+
foreach ($reflection->getProperties() as $property) {
23+
$propertyResult = $this->analyzeProperty($object, $property);
24+
if (null !== $propertyResult) {
25+
$results[$property->getName()] = $propertyResult;
26+
}
27+
}
28+
29+
return $results;
30+
} catch (\ReflectionException $e) {
31+
throw new PropertyInspectionException('Failed to analyze object: ' . $e->getMessage(), 0, $e);
32+
} catch (\Error $e) {
33+
throw new PropertyInspectionException('An error occurred during object analysis: ' . $e->getMessage(), 0, $e);
34+
}
35+
}
36+
37+
private function analyzeProperty(object $object, \ReflectionProperty $property): ?array
38+
{
39+
$attributes = $property->getAttributes($this->attributeClass, \ReflectionAttribute::IS_INSTANCEOF);
40+
if (empty($attributes)) {
41+
return null;
42+
}
43+
44+
$property->setAccessible(true);
45+
$propertyValue = $property->getValue($object);
46+
47+
return [
48+
'value' => $propertyValue,
49+
'attributes' => array_map(
50+
static fn (\ReflectionAttribute $attr): object => $attr->newInstance(),
51+
$attributes
52+
),
53+
];
54+
}
55+
}

src/Contract/AttributeAnalyzer.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\PropertyInspector\Contract;
6+
7+
interface AttributeAnalyzer
8+
{
9+
/**
10+
* Analyzes an object for specific attributes on its properties.
11+
*
12+
* @param object $object The object to be analyzed
13+
*
14+
* @throws \ReflectionException If there's an error analyzing the object
15+
*
16+
* @return array<string, array{value: mixed, attributes: array<object>}> An associative array with the analysis results
17+
*/
18+
public function analyzeObject(object $object): array;
19+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\PropertyInspector\Contract;
6+
7+
interface PropertyAttributeHandler
8+
{
9+
/**
10+
* Handles an attribute found on a property.
11+
*
12+
* @param object $object The object being inspected
13+
* @param string $propertyName The name of the property
14+
* @param object $attribute The found attribute
15+
* @param mixed $value The property value
16+
*
17+
* @return mixed The result of handling the attribute
18+
*/
19+
public function handleAttribute(object $object, string $propertyName, object $attribute, mixed $value): mixed;
20+
}

src/Contract/PropertyInspector.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\PropertyInspector\Contract;
6+
7+
use KaririCode\PropertyInspector\Exception\PropertyInspectionException;
8+
9+
interface PropertyInspector
10+
{
11+
/**
12+
* Inspects an object and processes its attributes.
13+
*
14+
* @param object $object The object to be inspected
15+
* @param PropertyAttributeHandler $handler The attribute handler
16+
*
17+
* @throws PropertyInspectionException If there's an error inspecting the object
18+
*
19+
* @return array<string, array<int, mixed>> The inspection results
20+
*/
21+
public function inspect(object $object, PropertyAttributeHandler $handler): array;
22+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\PropertyInspector\Exception;
6+
7+
class PropertyInspectionException extends \RuntimeException
8+
{
9+
}

src/PropertyInspector.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\PropertyInspector;
6+
7+
use KaririCode\PropertyInspector\Contract\AttributeAnalyzer;
8+
use KaririCode\PropertyInspector\Contract\PropertyAttributeHandler;
9+
use KaririCode\PropertyInspector\Contract\PropertyInspector as PropertyInspectorContract;
10+
use KaririCode\PropertyInspector\Exception\PropertyInspectionException;
11+
12+
final class PropertyInspector implements PropertyInspectorContract
13+
{
14+
public function __construct(private readonly AttributeAnalyzer $attributeAnalyzer)
15+
{
16+
}
17+
18+
public function inspect(object $object, PropertyAttributeHandler $handler): array
19+
{
20+
try {
21+
$analysisResults = $this->attributeAnalyzer->analyzeObject($object);
22+
$handledResults = [];
23+
24+
foreach ($analysisResults as $propertyName => $propertyData) {
25+
foreach ($propertyData['attributes'] as $attribute) {
26+
$result = $handler->handleAttribute($object, $propertyName, $attribute, $propertyData['value']);
27+
if (null !== $result) {
28+
$handledResults[$propertyName][] = $result;
29+
}
30+
}
31+
}
32+
33+
return $handledResults;
34+
} catch (\ReflectionException $e) {
35+
throw new PropertyInspectionException('Failed to analyze object: ' . $e->getMessage(), 0, $e);
36+
} catch (\Exception $e) {
37+
throw new PropertyInspectionException('An error occurred during object analysis: ' . $e->getMessage(), 0, $e);
38+
} catch (\Error $e) {
39+
throw new PropertyInspectionException('An error occurred during object analysis: ' . $e->getMessage(), 0, $e);
40+
}
41+
}
42+
}

tests/AttributeAnalyzerTest.php

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\PropertyInspector\Tests;
6+
7+
use Attribute;
8+
use KaririCode\PropertyInspector\AttributeAnalyzer;
9+
use KaririCode\PropertyInspector\Contract\AttributeAnalyzer as AttributeAnalyzerContract;
10+
use KaririCode\PropertyInspector\Exception\PropertyInspectionException;
11+
use PHPUnit\Framework\TestCase;
12+
13+
#[\Attribute()]
14+
class TestAttribute
15+
{
16+
}
17+
18+
class TestObject
19+
{
20+
#[TestAttribute]
21+
public string $testProperty = 'test value';
22+
23+
private string $privateProperty = 'private value';
24+
}
25+
26+
final class AttributeAnalyzerTest extends TestCase
27+
{
28+
private AttributeAnalyzerContract $analyzer;
29+
30+
protected function setUp(): void
31+
{
32+
$this->analyzer = new AttributeAnalyzer(TestAttribute::class);
33+
}
34+
35+
public function testAnalyzeObject(): void
36+
{
37+
$object = new TestObject();
38+
$result = $this->analyzer->analyzeObject($object);
39+
40+
$this->assertArrayHasKey('testProperty', $result);
41+
$this->assertEquals('test value', $result['testProperty']['value']);
42+
$this->assertInstanceOf(TestAttribute::class, $result['testProperty']['attributes'][0]);
43+
}
44+
45+
public function testAnalyzeObjectWithNoAttributes(): void
46+
{
47+
$object = new class {
48+
public string $propertyWithoutAttribute = 'no attribute';
49+
};
50+
51+
$result = $this->analyzer->analyzeObject($object);
52+
53+
$this->assertEmpty($result);
54+
}
55+
56+
public function testAnalyzeObjectWithPrivateProperty(): void
57+
{
58+
$object = new TestObject();
59+
$result = $this->analyzer->analyzeObject($object);
60+
61+
$this->assertArrayNotHasKey('privateProperty', $result);
62+
}
63+
64+
public function testReflectionExceptionThrownDuringAnalyzeObject(): void
65+
{
66+
// Define a fake attribute class for testing
67+
$attributeClass = 'FakeAttributeClass';
68+
69+
// Create the AttributeAnalyzer with the fake attribute class
70+
$analyzer = new AttributeAnalyzer($attributeClass);
71+
72+
// Simulate an object that will trigger a ReflectionException
73+
$object = new class {
74+
private $inaccessibleProperty;
75+
76+
public function __construct()
77+
{
78+
// Simulating an inaccessible property that will cause ReflectionException
79+
$this->inaccessibleProperty = null;
80+
}
81+
};
82+
83+
// We expect a PropertyInspectionException due to ReflectionException
84+
$this->expectException(PropertyInspectionException::class);
85+
$this->expectExceptionMessage('An error occurred during object analysis: Class "FakeAttributeClass" not found');
86+
87+
// Execute the analyzeObject method, which should trigger the exception
88+
$analyzer->analyzeObject($object);
89+
}
90+
91+
public function testErrorThrownDuringAnalyzeProperty(): void
92+
{
93+
// Define a fake attribute class for testing
94+
$attributeClass = 'FakeAttributeClass';
95+
96+
// Create the AttributeAnalyzer with the fake attribute class
97+
$analyzer = new AttributeAnalyzer($attributeClass);
98+
99+
// Simulate an object that will trigger an Error during property analysis
100+
$object = new class {
101+
private $errorProperty;
102+
103+
public function __construct()
104+
{
105+
// Simulating an error in the property that will cause an Error during reflection
106+
$this->errorProperty = null;
107+
}
108+
};
109+
110+
// Mock Reflection to throw an error during attribute analysis
111+
$reflectionPropertyMock = $this->createMock(\ReflectionProperty::class);
112+
$reflectionPropertyMock->method('getAttributes')
113+
->willThrowException(new \Error('Simulated Error'));
114+
115+
// We expect a PropertyInspectionException due to the Error
116+
$this->expectException(PropertyInspectionException::class);
117+
$this->expectExceptionMessage('An error occurred during object analysis: Class "FakeAttributeClass" not found');
118+
119+
// Execute the analyzeObject method, which should trigger the exception
120+
$analyzer->analyzeObject($object);
121+
}
122+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\PropertyInspector\Tests\Exception;
6+
7+
use KaririCode\PropertyInspector\Contract\AttributeAnalyzer as AttributeAnalyzerInterface;
8+
use KaririCode\PropertyInspector\Contract\PropertyAttributeHandler;
9+
use KaririCode\PropertyInspector\Exception\PropertyInspectionException;
10+
use KaririCode\PropertyInspector\PropertyInspector;
11+
use PHPUnit\Framework\TestCase;
12+
13+
class ReflectionExceptionTest extends TestCase
14+
{
15+
public function testReflectionExceptionThrownDuringInspection(): void
16+
{
17+
$attributeAnalyzer = $this->createMock(AttributeAnalyzerInterface::class);
18+
$handler = $this->createMock(PropertyAttributeHandler::class);
19+
$inspector = new PropertyInspector($attributeAnalyzer);
20+
21+
$object = new \stdClass();
22+
23+
$attributeAnalyzer->method('analyzeObject')
24+
->willThrowException(new \ReflectionException('Simulated ReflectionException'));
25+
26+
$this->expectException(PropertyInspectionException::class);
27+
$this->expectExceptionMessage('Failed to analyze object: Simulated ReflectionException');
28+
29+
$inspector->inspect($object, $handler);
30+
}
31+
32+
public function testReflectionExceptionThrownDuringAnalyzeObject(): void
33+
{
34+
$attributeAnalyzer = $this->createMock(AttributeAnalyzerInterface::class);
35+
$object = new \stdClass();
36+
37+
$attributeAnalyzer->method('analyzeObject')
38+
->willThrowException(new PropertyInspectionException('Failed to analyze property: Class "FakeAttributeClass" not found'));
39+
40+
$this->expectException(PropertyInspectionException::class);
41+
$this->expectExceptionMessage('Failed to analyze property: Class "FakeAttributeClass" not found');
42+
43+
$attributeAnalyzer->analyzeObject($object);
44+
}
45+
46+
public function testReflectionExceptionInAnalyzeObject(): void
47+
{
48+
$attributeAnalyzer = $this->createMock(AttributeAnalyzerInterface::class);
49+
$object = new \stdClass();
50+
51+
$attributeAnalyzer->method('analyzeObject')
52+
->willThrowException(new \ReflectionException('Test ReflectionException'));
53+
54+
$inspector = new PropertyInspector($attributeAnalyzer);
55+
$handler = $this->createMock(PropertyAttributeHandler::class);
56+
57+
$this->expectException(PropertyInspectionException::class);
58+
$this->expectExceptionMessage('Failed to analyze object: Test ReflectionException');
59+
60+
$inspector->inspect($object, $handler);
61+
}
62+
63+
public function testErrorInAnalyzeObject(): void
64+
{
65+
$attributeAnalyzer = $this->createMock(AttributeAnalyzerInterface::class);
66+
$object = new \stdClass();
67+
68+
$attributeAnalyzer->method('analyzeObject')
69+
->willThrowException(new \Error('Test Error'));
70+
71+
$inspector = new PropertyInspector($attributeAnalyzer);
72+
$handler = $this->createMock(PropertyAttributeHandler::class);
73+
74+
$this->expectException(PropertyInspectionException::class);
75+
$this->expectExceptionMessage('An error occurred during object analysis: Test Error');
76+
77+
$inspector->inspect($object, $handler);
78+
}
79+
80+
public function testErrorThrownDuringInspection(): void
81+
{
82+
$attributeAnalyzer = $this->createMock(AttributeAnalyzerInterface::class);
83+
$handler = $this->createMock(PropertyAttributeHandler::class);
84+
$inspector = new PropertyInspector($attributeAnalyzer);
85+
86+
$object = new \stdClass();
87+
88+
$attributeAnalyzer->method('analyzeObject')
89+
->willThrowException(new \Error('Simulated Error'));
90+
91+
$this->expectException(PropertyInspectionException::class);
92+
$this->expectExceptionMessage('An error occurred during object analysis: Simulated Error');
93+
94+
$inspector->inspect($object, $handler);
95+
}
96+
}

0 commit comments

Comments
 (0)