Skip to content

Commit c2d8dc3

Browse files
committed
ObjectHelpers: SmartObject helpers taken out of ObjectMixin
1 parent 2066357 commit c2d8dc3

9 files changed

+322
-259
lines changed

src/Utils/Image.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ public function __call(string $name, array $args)
578578
{
579579
$function = 'image' . $name;
580580
if (!function_exists($function)) {
581-
ObjectMixin::strictCall(get_class($this), $name);
581+
ObjectHelpers::strictCall(get_class($this), $name);
582582
}
583583

584584
foreach ($args as $key => $value) {

src/Utils/ObjectHelpers.php

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Nette\Utils;
11+
12+
use Nette;
13+
use Nette\MemberAccessException;
14+
15+
16+
/**
17+
* Nette\SmartObject helpers.
18+
*/
19+
class ObjectHelpers
20+
{
21+
use Nette\StaticClass;
22+
23+
/**
24+
* @throws MemberAccessException
25+
*/
26+
public static function strictGet(string $class, string $name)
27+
{
28+
$rc = new \ReflectionClass($class);
29+
$hint = self::getSuggestion(array_merge(
30+
array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
31+
self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
32+
), $name);
33+
throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
34+
}
35+
36+
37+
/**
38+
* @throws MemberAccessException
39+
*/
40+
public static function strictSet(string $class, string $name)
41+
{
42+
$rc = new \ReflectionClass($class);
43+
$hint = self::getSuggestion(array_merge(
44+
array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
45+
self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
46+
), $name);
47+
throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
48+
}
49+
50+
51+
/**
52+
* @throws MemberAccessException
53+
*/
54+
public static function strictCall(string $class, string $method, array $additionalMethods = [])
55+
{
56+
$hint = self::getSuggestion(array_merge(
57+
get_class_methods($class),
58+
self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'),
59+
$additionalMethods
60+
), $method);
61+
62+
if (method_exists($class, $method)) { // called parent::$method()
63+
$class = 'parent';
64+
}
65+
throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
66+
}
67+
68+
69+
/**
70+
* @throws MemberAccessException
71+
*/
72+
public static function strictStaticCall(string $class, string $method)
73+
{
74+
$hint = self::getSuggestion(
75+
array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }),
76+
$method
77+
);
78+
throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
79+
}
80+
81+
82+
/**
83+
* Returns array of magic properties defined by annotation @property.
84+
* @return array of [name => bit mask]
85+
* @internal
86+
*/
87+
public static function getMagicProperties(string $class): array
88+
{
89+
static $cache;
90+
$props = &$cache[$class];
91+
if ($props !== NULL) {
92+
return $props;
93+
}
94+
95+
$rc = new \ReflectionClass($class);
96+
preg_match_all(
97+
'~^ [ \t*]* @property(|-read|-write) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx',
98+
(string) $rc->getDocComment(), $matches, PREG_SET_ORDER
99+
);
100+
101+
$props = [];
102+
foreach ($matches as list(, $type, $name)) {
103+
$uname = ucfirst($name);
104+
$write = $type !== '-read'
105+
&& $rc->hasMethod($nm = 'set' . $uname)
106+
&& ($rm = $rc->getMethod($nm)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic();
107+
$read = $type !== '-write'
108+
&& ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname))
109+
&& ($rm = $rc->getMethod($nm)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic();
110+
111+
if ($read || $write) {
112+
$props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3;
113+
}
114+
}
115+
116+
foreach ($rc->getTraits() as $trait) {
117+
$props += self::getMagicProperties($trait->getName());
118+
}
119+
120+
if ($parent = get_parent_class($class)) {
121+
$props += self::getMagicProperties($parent);
122+
}
123+
return $props;
124+
}
125+
126+
127+
/**
128+
* Finds the best suggestion (for 8-bit encoding).
129+
* @return string|NULL
130+
* @internal
131+
*/
132+
public static function getSuggestion(array $possibilities, string $value)
133+
{
134+
$norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '', $value);
135+
$best = NULL;
136+
$min = (strlen($value) / 4 + 1) * 10 + .1;
137+
foreach (array_unique($possibilities, SORT_REGULAR) as $item) {
138+
$item = $item instanceof \Reflector ? $item->getName() : $item;
139+
if ($item !== $value && (
140+
($len = levenshtein($item, $value, 10, 11, 10)) < $min
141+
|| ($len = levenshtein(preg_replace($re, '', $item), $norm, 10, 11, 10) + 20) < $min
142+
)) {
143+
$min = $len;
144+
$best = $item;
145+
}
146+
}
147+
return $best;
148+
}
149+
150+
151+
private static function parseFullDoc(\ReflectionClass $rc, string $pattern): array
152+
{
153+
do {
154+
$doc[] = $rc->getDocComment();
155+
$traits = $rc->getTraits();
156+
while ($trait = array_pop($traits)) {
157+
$doc[] = $trait->getDocComment();
158+
$traits += $trait->getTraits();
159+
}
160+
} while ($rc = $rc->getParentClass());
161+
return preg_match_all($pattern, implode($doc), $m) ? $m[1] : [];
162+
}
163+
164+
165+
/**
166+
* Checks if the public non-static property exists.
167+
* @return bool|'event'
168+
* @internal
169+
*/
170+
public static function hasProperty(string $class, string $name)
171+
{
172+
static $cache;
173+
$prop = &$cache[$class][$name];
174+
if ($prop === NULL) {
175+
$prop = FALSE;
176+
try {
177+
$rp = new \ReflectionProperty($class, $name);
178+
if ($rp->isPublic() && !$rp->isStatic()) {
179+
$prop = $name >= 'onA' && $name < 'on_' ? 'event' : TRUE;
180+
}
181+
} catch (\ReflectionException $e) {
182+
}
183+
}
184+
return $prop;
185+
}
186+
187+
}

0 commit comments

Comments
 (0)