Skip to content

Commit ba22991

Browse files
authored
Move property related code into properties class
* Add shortcut commands to composer.json * Add missing copyright banner * Moved properties-related code out of ClassConf into separate Properties class
1 parent e486f69 commit ba22991

File tree

5 files changed

+206
-149
lines changed

5 files changed

+206
-149
lines changed

composer.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,9 @@
3636
"psr-4": {
3737
"margusk\\Accessors\\Tests\\": "tests/"
3838
}
39+
},
40+
"scripts": {
41+
"tests": "vendor/bin/phpunit",
42+
"phpstan": "vendor/bin/phpstan analyse src/ tests/ -l 9"
3943
}
4044
}

src/Accessible.php

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public function __call(string $method, array $args): mixed
4242
$lcaseMethod = strtolower($method);
4343

4444
// Try to extract accessor method from magic method name
45-
if (!in_array(($accessorMethod = substr($lcaseMethod, 0, 3)), ['get', 'set'], true)
45+
if (
46+
!in_array(($accessorMethod = substr($lcaseMethod, 0, 3)), ['get', 'set'], true)
4647
&& 'with' !== ($accessorMethod = substr($lcaseMethod, 0, 4))
4748
&& !in_array(($accessorMethod = substr($lcaseMethod, 0, 5)), ['unset', 'isset'], true)
4849
) {
@@ -57,7 +58,8 @@ public function __call(string $method, array $args): mixed
5758

5859
// Check if the call is multi-property accessor, that is if first
5960
// argument is array and accessor method is set at this point and is "set", "with" or "unset"
60-
if ('' === $propertyName
61+
if (
62+
'' === $propertyName
6163
&& $nArgs > 0
6264
&& is_array(current($args))
6365
&& ($accessorMethodIsSetOrWith || 'unset' === $accessorMethod)
@@ -74,7 +76,10 @@ public function __call(string $method, array $args): mixed
7476

7577
// Check if whole method name is property name like
7678
// $obj->somePropertyName('somevalue')
77-
} elseif (null === $accessorMethod && null !== $classConf->findPropertyConf($propertyName, true)) {
79+
} elseif (
80+
null === $accessorMethod
81+
&& null !== $classConf->properties()->findConf($propertyName, true)
82+
) {
7883
// If there are zero arguments, then interpret the call as Getter
7984
// If there are arguments, then it's Setter
8085
if ($nArgs > 0) {
@@ -163,13 +168,14 @@ public function __call(string $method, array $args): mixed
163168
);
164169
}
165170

166-
$propertyConf = $classConf->findPropertyConf($propertyName, $propertyNameCI);
171+
$propertyConf = $classConf->properties()->findConf($propertyName, $propertyNameCI);
167172
$immutable = ($propertyConf?->isImmutable()) ?? false;
168173

169174
// Check if mutable/immutable property was called using correct method:
170175
// - mutable properties must be accessed using "set"
171176
// - immutable properties must be accessed using "with"
172-
if (($immutable === true && 'set' === $accessorMethod)
177+
if (
178+
($immutable === true && 'set' === $accessorMethod)
173179
|| ($immutable === false && 'with' === $accessorMethod)
174180
) {
175181
if ($immutable) {
@@ -204,7 +210,7 @@ public function __call(string $method, array $args): mixed
204210
);
205211
}
206212

207-
$propertyConf = $classConf->findPropertyConf($propertyName, $propertyNameCI);
213+
$propertyConf = $classConf->properties()->findConf($propertyName, $propertyNameCI);
208214
$result = $accessorImpl($result, $propertyName, $propertyConf);
209215
}
210216
}
@@ -221,7 +227,7 @@ public function __call(string $method, array $args): mixed
221227
public function __get(string $propertyName): mixed
222228
{
223229
$classConf = ClassConf::factory(static::class);
224-
$propertyConf = $classConf->findPropertyConf($propertyName);
230+
$propertyConf = $classConf->properties()->findConf($propertyName);
225231

226232
return ($classConf->getGetter())($this, $propertyName, $propertyConf);
227233
}
@@ -236,7 +242,7 @@ public function __get(string $propertyName): mixed
236242
public function __set(string $propertyName, mixed $propertyValue): void
237243
{
238244
$classConf = ClassConf::factory(static::class);
239-
$propertyConf = $classConf->findPropertyConf($propertyName);
245+
$propertyConf = $classConf->properties()->findConf($propertyName);
240246
$immutable = $propertyConf?->isImmutable();
241247

242248
if ($immutable) {
@@ -258,7 +264,7 @@ public function __set(string $propertyName, mixed $propertyValue): void
258264
public function __isset(string $propertyName): bool
259265
{
260266
$classConf = ClassConf::factory(static::class);
261-
$propertyConf = $classConf->findPropertyConf($propertyName);
267+
$propertyConf = $classConf->properties()->findConf($propertyName);
262268

263269
return ($classConf->getIsSetter())($this, $propertyName, $propertyConf);
264270
}
@@ -272,7 +278,7 @@ public function __isset(string $propertyName): bool
272278
public function __unset(string $propertyName): void
273279
{
274280
$classConf = ClassConf::factory(static::class);
275-
$propertyConf = $classConf->findPropertyConf($propertyName);
281+
$propertyConf = $classConf->properties()->findConf($propertyName);
276282

277283
($classConf->getUnSetter())($this, $propertyName, $propertyConf);
278284
}

src/ClassConf.php

Lines changed: 13 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,11 @@
1414

1515
use Closure;
1616
use margusk\Accessors\Exception\InvalidArgumentException;
17-
use PHPStan\PhpDocParser\Parser\PhpDocParser;
18-
use PHPStan\PhpDocParser\Parser\TypeParser;
19-
use PHPStan\PhpDocParser\Parser\ConstExprParser;
20-
use PHPStan\PhpDocParser\Parser\TokenIterator;
21-
use PHPStan\PhpDocParser\Lexer\Lexer;
22-
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
23-
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
2417
use ReflectionClass;
2518
use ReflectionException;
26-
use ReflectionMethod;
27-
use ReflectionProperty;
2819

2920
use function call_user_func;
3021
use function get_parent_class;
31-
use function is_string;
32-
use function preg_match;
33-
use function str_starts_with;
34-
use function strtolower;
35-
use function substr;
3622

3723
final class ClassConf
3824
{
@@ -42,14 +28,8 @@ final class ClassConf
4228
/** @var Attributes */
4329
private Attributes $attributes;
4430

45-
/** @var ReflectionClass<object> */
46-
private ReflectionClass $rfClass;
47-
48-
/** @var Property[] */
49-
private array $properties = [];
50-
51-
/** @var Property[] */
52-
private array $propertiesByLcase = [];
31+
/** @var Properties */
32+
private Properties $properties;
5333

5434
/** @var Closure */
5535
private Closure $getter;
@@ -72,71 +52,21 @@ private function __construct(
7252
protected string $name
7353
) {
7454
/* Verify that the specified class is valid in every aspect */
75-
$this->rfClass = new ReflectionClass($this->name);
55+
$rfClass = new ReflectionClass($this->name);
7656

77-
/* Parse attributes of current class */
78-
$this->attributes = Attributes::fromReflection($this->rfClass);
57+
/* First parse attributes of current class */
58+
$this->attributes = Attributes::fromReflection($rfClass);
7959

80-
/* Require parent class to be parsed before current class, ... */
60+
/* Next require parent class to be initialized before current, ... */
8161
$parentName = get_parent_class($this->name);
8262

83-
/* ...because attributes from current class need to be merged with parent one's */
63+
/* ...because attributes from parent class need to be merged into current */
8464
if (false !== $parentName) {
8565
$parent = self::factory($parentName);
8666
$this->attributes = $this->attributes->mergeWithParent($parent->attributes);
8767
}
8868

89-
/* Learn from DocBlock comments which properties should be exposed and how (read-only,write-only or both) */
90-
$docBlockAttributes = $this->parseDocBlock($this->rfClass);
91-
92-
/**
93-
* Collect all manual accessor endpoints.
94-
*
95-
* @var array<string, array<string, string>> $accessorEndpoints
96-
*/
97-
$accessorEndpoints = [];
98-
99-
foreach (
100-
$this->rfClass->getMethods(
101-
ReflectionMethod::IS_PROTECTED | ReflectionMethod::IS_PRIVATE | ReflectionMethod::IS_PUBLIC
102-
) as $rfMethod
103-
) {
104-
if (!$rfMethod->isStatic()
105-
&& preg_match(
106-
'/^(set|get|isset|unset|with)(.+)/',
107-
strtolower($rfMethod->name),
108-
$matches
109-
)
110-
) {
111-
$accessorEndpoints[(string)$matches[2]][(string)$matches[1]] = $rfMethod->name;
112-
}
113-
}
114-
115-
/**
116-
* Find all class properties.
117-
*
118-
* Provide accessor functionality only for private and protected properties.
119-
*
120-
* Although accessors for public properties are not provided (because it makes the behaviour unconsistent),
121-
* we'll need to remember them along with private and protected properties, so in case they are accessed,
122-
* informative error can be reported.
123-
*/
124-
foreach (
125-
$this->rfClass->getProperties(
126-
ReflectionMethod::IS_PRIVATE | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC
127-
) as $rfProperty
128-
) {
129-
$name = $rfProperty->getName();
130-
$nameLowerCase = strtolower($name);
131-
132-
$this->properties[$name] = new Property(
133-
$rfProperty,
134-
($docBlockAttributes[$name] ?? $this->attributes),
135-
($accessorEndpoints[$nameLowerCase] ?? [])
136-
);
137-
138-
$this->propertiesByLcase[$nameLowerCase] = $this->properties[$name];
139-
}
69+
$this->properties = new Properties($rfClass, $this->attributes);
14070

14171
$this->getter = $this->createGetter();
14272
$this->setter = $this->createSetter();
@@ -195,7 +125,9 @@ private function createSetter(): Closure
195125
if (null !== $endpoint) {
196126
$result = $object->{$endpoint}($value);
197127

198-
if ('with' === $accessorMethod && ($result instanceof (self::class))
128+
if (
129+
'with' === $accessorMethod
130+
&& ($result instanceof (self::class))
199131
) {
200132
$object = $result;
201133
}
@@ -291,15 +223,9 @@ public static function factory(string $name): ClassConf
291223
return self::$classes[$name];
292224
}
293225

294-
public function findPropertyConf(string $name, bool $caseInsensitiveSearch = false): ?Property
226+
public function properties(): Properties
295227
{
296-
if ($caseInsensitiveSearch) {
297-
$propertyConf = $this->propertiesByLcase[strtolower($name)] ?? null;
298-
} else {
299-
$propertyConf = $this->properties[$name] ?? null;
300-
}
301-
302-
return $propertyConf;
228+
return $this->properties;
303229
}
304230

305231
public function getGetter(): Closure
@@ -321,56 +247,4 @@ public function getUnSetter(): Closure
321247
{
322248
return $this->unSetter;
323249
}
324-
325-
/**
326-
* @param ReflectionClass<object> $rfClass
327-
*
328-
* @return array<string, Attributes>
329-
*/
330-
private function parseDocBlock(ReflectionClass $rfClass): array
331-
{
332-
static $docBlockParser = null;
333-
static $docBlockLexer = null;
334-
335-
$docComment = $rfClass->getDocComment();
336-
337-
if (!is_string($docComment)) {
338-
return [];
339-
}
340-
341-
if (null === $docBlockParser) {
342-
$constExprParser = new ConstExprParser();
343-
344-
$docBlockParser = new PhpDocParser(
345-
new TypeParser($constExprParser),
346-
$constExprParser
347-
);
348-
349-
$docBlockLexer = new Lexer();
350-
}
351-
352-
$node = $docBlockParser->parse(
353-
new TokenIterator(
354-
$docBlockLexer->tokenize($docComment)
355-
)
356-
);
357-
358-
$result = [];
359-
360-
foreach ($node->children as $childNode) {
361-
if ($childNode instanceof PhpDocTagNode
362-
&& $childNode->value instanceof PropertyTagValueNode
363-
&& str_starts_with($childNode->value->propertyName, '$')) {
364-
365-
$attributes = Attributes::fromDocBlock($childNode);
366-
367-
if (null !== $attributes) {
368-
$attributes->mergeWithParent($this->attributes);
369-
$result[substr($childNode->value->propertyName, 1)] = $attributes;
370-
}
371-
}
372-
}
373-
374-
return $result;
375-
}
376250
}

0 commit comments

Comments
 (0)