friendship ended with social-app. php is my new best friend
at main 7.2 kB view raw
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 8declare(strict_types=1); 9 10namespace Nette\Utils; 11 12use Nette; 13use Nette\MemberAccessException; 14use function array_filter, array_merge, array_pop, array_unique, get_class_methods, get_parent_class, implode, is_a, levenshtein, method_exists, preg_match_all, preg_replace, strlen, ucfirst; 15use const PREG_SET_ORDER, SORT_REGULAR; 16 17 18/** 19 * Nette\SmartObject helpers. 20 * @internal 21 */ 22final class ObjectHelpers 23{ 24 use Nette\StaticClass; 25 26 /** 27 * @return never 28 * @throws MemberAccessException 29 */ 30 public static function strictGet(string $class, string $name): void 31 { 32 $rc = new \ReflectionClass($class); 33 $hint = self::getSuggestion(array_merge( 34 array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), 35 self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), 36 ), $name); 37 throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); 38 } 39 40 41 /** 42 * @return never 43 * @throws MemberAccessException 44 */ 45 public static function strictSet(string $class, string $name): void 46 { 47 $rc = new \ReflectionClass($class); 48 $hint = self::getSuggestion(array_merge( 49 array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), 50 self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), 51 ), $name); 52 throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); 53 } 54 55 56 /** 57 * @return never 58 * @throws MemberAccessException 59 */ 60 public static function strictCall(string $class, string $method, array $additionalMethods = []): void 61 { 62 $trace = debug_backtrace(0, 3); // suppose this method is called from __call() 63 $context = ($trace[1]['function'] ?? null) === '__call' 64 ? ($trace[2]['class'] ?? null) 65 : null; 66 67 if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method() 68 $class = get_parent_class($context); 69 } 70 71 if (method_exists($class, $method)) { // insufficient visibility 72 $rm = new \ReflectionMethod($class, $method); 73 $visibility = $rm->isPrivate() 74 ? 'private ' 75 : ($rm->isProtected() ? 'protected ' : ''); 76 throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.')); 77 78 } else { 79 $hint = self::getSuggestion(array_merge( 80 get_class_methods($class), 81 self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:static[ \t]+)?(?:\S+[ \t]+)??(\w+)\(~m'), 82 $additionalMethods, 83 ), $method); 84 throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); 85 } 86 } 87 88 89 /** 90 * @return never 91 * @throws MemberAccessException 92 */ 93 public static function strictStaticCall(string $class, string $method): void 94 { 95 $trace = debug_backtrace(0, 3); // suppose this method is called from __callStatic() 96 $context = ($trace[1]['function'] ?? null) === '__callStatic' 97 ? ($trace[2]['class'] ?? null) 98 : null; 99 100 if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method() 101 $class = get_parent_class($context); 102 } 103 104 if (method_exists($class, $method)) { // insufficient visibility 105 $rm = new \ReflectionMethod($class, $method); 106 $visibility = $rm->isPrivate() 107 ? 'private ' 108 : ($rm->isProtected() ? 'protected ' : ''); 109 throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.')); 110 111 } else { 112 $hint = self::getSuggestion( 113 array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), fn($m) => $m->isStatic()), 114 $method, 115 ); 116 throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); 117 } 118 } 119 120 121 /** 122 * Returns array of magic properties defined by annotation @property. 123 * @return array of [name => bit mask] 124 * @internal 125 */ 126 public static function getMagicProperties(string $class): array 127 { 128 static $cache; 129 $props = &$cache[$class]; 130 if ($props !== null) { 131 return $props; 132 } 133 134 $rc = new \ReflectionClass($class); 135 preg_match_all( 136 '~^ [ \t*]* @property(|-read|-write|-deprecated) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx', 137 (string) $rc->getDocComment(), 138 $matches, 139 PREG_SET_ORDER, 140 ); 141 142 $props = []; 143 foreach ($matches as [, $type, $name]) { 144 $uname = ucfirst($name); 145 $write = $type !== '-read' 146 && $rc->hasMethod($nm = 'set' . $uname) 147 && ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic(); 148 $read = $type !== '-write' 149 && ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname)) 150 && ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic(); 151 152 if ($read || $write) { 153 $props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3 | ($type === '-deprecated') << 4; 154 } 155 } 156 157 foreach ($rc->getTraits() as $trait) { 158 $props += self::getMagicProperties($trait->name); 159 } 160 161 if ($parent = get_parent_class($class)) { 162 $props += self::getMagicProperties($parent); 163 } 164 165 return $props; 166 } 167 168 169 /** 170 * Finds the best suggestion (for 8-bit encoding). 171 * @param (\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionClass|\ReflectionProperty|string)[] $possibilities 172 * @internal 173 */ 174 public static function getSuggestion(array $possibilities, string $value): ?string 175 { 176 $norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '+', $value); 177 $best = null; 178 $min = (strlen($value) / 4 + 1) * 10 + .1; 179 foreach (array_unique($possibilities, SORT_REGULAR) as $item) { 180 $item = $item instanceof \Reflector ? $item->name : $item; 181 if ($item !== $value && ( 182 ($len = levenshtein($item, $value, 10, 11, 10)) < $min 183 || ($len = levenshtein(preg_replace($re, '*', $item), $norm, 10, 11, 10)) < $min 184 )) { 185 $min = $len; 186 $best = $item; 187 } 188 } 189 190 return $best; 191 } 192 193 194 private static function parseFullDoc(\ReflectionClass $rc, string $pattern): array 195 { 196 do { 197 $doc[] = $rc->getDocComment(); 198 $traits = $rc->getTraits(); 199 while ($trait = array_pop($traits)) { 200 $doc[] = $trait->getDocComment(); 201 $traits += $trait->getTraits(); 202 } 203 } while ($rc = $rc->getParentClass()); 204 205 return preg_match_all($pattern, implode('', $doc), $m) ? $m[1] : []; 206 } 207 208 209 /** 210 * Checks if the public non-static property exists. 211 * Returns 'event' if the property exists and has event like name 212 * @internal 213 */ 214 public static function hasProperty(string $class, string $name): bool|string 215 { 216 static $cache; 217 $prop = &$cache[$class][$name]; 218 if ($prop === null) { 219 $prop = false; 220 try { 221 $rp = new \ReflectionProperty($class, $name); 222 if ($rp->isPublic() && !$rp->isStatic()) { 223 $prop = $name >= 'onA' && $name < 'on_' ? 'event' : true; 224 } 225 } catch (\ReflectionException $e) { 226 } 227 } 228 229 return $prop; 230 } 231}