Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Utils
  • none
  • Tracy
    • Bridges
      • Nette

Classes

  • ArrayHash
  • ArrayList
  • Arrays
  • Callback
  • DateTime
  • FileSystem
  • Finder
  • Html
  • Image
  • Json
  • LimitedScope
  • MimeTypeDetector
  • ObjectMixin
  • Paginator
  • Random
  • Strings
  • TokenIterator
  • Tokenizer
  • Validators

Interfaces

  • IHtmlString

Exceptions

  • AssertionException
  • ImageException
  • JsonException
  • RegexpException
  • TokenizerException
  • UnknownImageFileException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  • Nette homepage
  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: namespace Nette\Utils;
  9: 
 10: use Nette;
 11: use Nette\MemberAccessException;
 12: 
 13: 
 14: /**
 15:  * Nette\Object behaviour mixin.
 16:  */
 17: class ObjectMixin
 18: {
 19:     /** @var array (name => 0 | bool | array)  used by getMethods() */
 20:     private static $methods;
 21: 
 22:     /** @var array (name => 'event' | TRUE)  used by hasProperty() */
 23:     private static $props;
 24: 
 25:     /** @var array (name => array(type => callback))  used by get|setExtensionMethod() */
 26:     private static $extMethods;
 27: 
 28: 
 29:     /**
 30:      * Static class - cannot be instantiated.
 31:      */
 32:     final public function __construct()
 33:     {
 34:         throw new Nette\StaticClassException;
 35:     }
 36: 
 37: 
 38:     /**
 39:      * __call() implementation.
 40:      * @param  object
 41:      * @param  string
 42:      * @param  array
 43:      * @return mixed
 44:      * @throws MemberAccessException
 45:      */
 46:     public static function call($_this, $name, $args)
 47:     {
 48:         $class = get_class($_this);
 49:         $isProp = self::hasProperty($class, $name);
 50: 
 51:         if ($name === '') {
 52:             throw new MemberAccessException("Call to class '$class' method without name.");
 53: 
 54:         } elseif ($isProp && $_this->$name instanceof \Closure) { // closure in property
 55:             return call_user_func_array($_this->$name, $args);
 56: 
 57:         } elseif ($isProp === 'event') { // calling event handlers
 58:             if (is_array($_this->$name) || $_this->$name instanceof \Traversable) {
 59:                 foreach ($_this->$name as $handler) {
 60:                     Callback::invokeArgs($handler, $args);
 61:                 }
 62:             } elseif ($_this->$name !== NULL) {
 63:                 throw new Nette\UnexpectedValueException("Property $class::$$name must be array or NULL, " . gettype($_this->$name) . ' given.');
 64:             }
 65: 
 66:         } elseif (($methods = & self::getMethods($class)) && isset($methods[$name]) && is_array($methods[$name])) { // magic @methods
 67:             list($op, $rp, $type) = $methods[$name];
 68:             if (count($args) !== ($op === 'get' ? 0 : 1)) {
 69:                 throw new Nette\InvalidArgumentException("$class::$name() expects " . ($op === 'get' ? 'no' : '1') . ' argument, ' . count($args) . ' given.');
 70: 
 71:             } elseif ($type && $args && !self::checkType($args[0], $type)) {
 72:                 throw new Nette\InvalidArgumentException("Argument passed to $class::$name() must be $type, " . gettype($args[0]) . ' given.');
 73:             }
 74: 
 75:             if ($op === 'get') {
 76:                 return $rp->getValue($_this);
 77:             } elseif ($op === 'set') {
 78:                 $rp->setValue($_this, $args[0]);
 79:             } elseif ($op === 'add') {
 80:                 $val = $rp->getValue($_this);
 81:                 $val[] = $args[0];
 82:                 $rp->setValue($_this, $val);
 83:             }
 84:             return $_this;
 85: 
 86:         } elseif ($cb = self::getExtensionMethod($class, $name)) { // extension methods
 87:             array_unshift($args, $_this);
 88:             return Callback::invokeArgs($cb, $args);
 89: 
 90:         } else {
 91:             $hint = self::getSuggestion(array_merge(
 92:                 get_class_methods($class),
 93:                 self::parseFullDoc($class, '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'),
 94:                 array_keys(self::getExtensionMethods($class))
 95:             ), $name);
 96: 
 97:             if (method_exists($class, $name)) { // called parent::$name()
 98:                 $class = 'parent';
 99:             }
100:             throw new MemberAccessException("Call to undefined method $class::$name()" . ($hint ? ", did you mean $hint()?" : '.'));
101:         }
102:     }
103: 
104: 
105:     /**
106:      * __callStatic() implementation.
107:      * @param  string
108:      * @param  string
109:      * @param  array
110:      * @return void
111:      * @throws MemberAccessException
112:      */
113:     public static function callStatic($class, $method, $args)
114:     {
115:         $hint = self::getSuggestion(array_filter(
116:             get_class_methods($class),
117:             function ($m) use ($class) { $rm = new \ReflectionMethod($class, $m); return $rm->isStatic(); }
118:         ), $method);
119:         throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
120:     }
121: 
122: 
123:     /**
124:      * __get() implementation.
125:      * @param  object
126:      * @param  string  property name
127:      * @return mixed   property value
128:      * @throws MemberAccessException if the property is not defined.
129:      */
130:     public static function & get($_this, $name)
131:     {
132:         $class = get_class($_this);
133:         $uname = ucfirst($name);
134:         $methods = & self::getMethods($class);
135: 
136:         if ($name === '') {
137:             throw new MemberAccessException("Cannot read a class '$class' property without name.");
138: 
139:         } elseif (isset($methods[$m = 'get' . $uname]) || isset($methods[$m = 'is' . $uname])) { // property getter
140:             if ($methods[$m] === 0) {
141:                 $rm = new \ReflectionMethod($class, $m);
142:                 $methods[$m] = $rm->returnsReference();
143:             }
144:             if ($methods[$m] === TRUE) {
145:                 return $_this->$m();
146:             } else {
147:                 $val = $_this->$m();
148:                 return $val;
149:             }
150: 
151:         } elseif (isset($methods[$name])) { // public method as closure getter
152:             if (preg_match('#^(is|get|has)([A-Z]|$)#', $name) && ($rm = new \ReflectionMethod($class, $name)) && !$rm->getNumberOfRequiredParameters()) {
153:                 $source = '';
154:                 foreach (debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE) as $item) {
155:                     if (isset($item['file']) && dirname($item['file']) !== __DIR__) {
156:                         $source = " in $item[file]:$item[line]";
157:                         break;
158:                     }
159:                 }
160:                 trigger_error("Did you forgot parentheses after $name$source?", E_USER_WARNING);
161:             }
162:             $val = Callback::closure($_this, $name);
163:             return $val;
164: 
165:         } elseif (isset($methods['set' . $uname])) { // strict class
166:             throw new MemberAccessException("Cannot read a write-only property $class::\$$name.");
167: 
168:         } else { // strict class
169:             $hint = self::getSuggestion(array_merge(
170:                 array_keys(get_class_vars($class)),
171:                 self::parseFullDoc($class, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
172:             ), $name);
173:             throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
174:         }
175:     }
176: 
177: 
178:     /**
179:      * __set() implementation.
180:      * @param  object
181:      * @param  string  property name
182:      * @param  mixed   property value
183:      * @return void
184:      * @throws MemberAccessException if the property is not defined or is read-only
185:      */
186:     public static function set($_this, $name, $value)
187:     {
188:         $class = get_class($_this);
189:         $uname = ucfirst($name);
190:         $methods = & self::getMethods($class);
191: 
192:         if ($name === '') {
193:             throw new MemberAccessException("Cannot write to a class '$class' property without name.");
194: 
195:         } elseif (self::hasProperty($class, $name)) { // unsetted property
196:             $_this->$name = $value;
197: 
198:         } elseif (isset($methods[$m = 'set' . $uname])) { // property setter
199:             $_this->$m($value);
200: 
201:         } elseif (isset($methods['get' . $uname]) || isset($methods['is' . $uname])) { // strict class
202:             throw new MemberAccessException("Cannot write to a read-only property $class::\$$name.");
203: 
204:         } else { // strict class
205:             $hint = self::getSuggestion(array_merge(
206:                 array_keys(get_class_vars($class)),
207:                 self::parseFullDoc($class, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
208:             ), $name);
209:             throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
210:         }
211:     }
212: 
213: 
214:     /**
215:      * __unset() implementation.
216:      * @param  object
217:      * @param  string  property name
218:      * @return void
219:      * @throws MemberAccessException
220:      */
221:     public static function remove($_this, $name)
222:     {
223:         $class = get_class($_this);
224:         if (!self::hasProperty($class, $name)) { // strict class
225:             throw new MemberAccessException("Cannot unset the property $class::\$$name.");
226:         }
227:     }
228: 
229: 
230:     /**
231:      * __isset() implementation.
232:      * @param  object
233:      * @param  string  property name
234:      * @return bool
235:      */
236:     public static function has($_this, $name)
237:     {
238:         $name = ucfirst($name);
239:         $methods = & self::getMethods(get_class($_this));
240:         return $name !== '' && (isset($methods['get' . $name]) || isset($methods['is' . $name]));
241:     }
242: 
243: 
244:     /**
245:      * Checks if the public non-static property exists.
246:      * @return mixed
247:      */
248:     private static function hasProperty($class, $name)
249:     {
250:         $prop = & self::$props[$class][$name];
251:         if ($prop === NULL) {
252:             $prop = FALSE;
253:             try {
254:                 $rp = new \ReflectionProperty($class, $name);
255:                 if ($rp->isPublic() && !$rp->isStatic()) {
256:                     $prop = $name >= 'onA' && $name < 'on_' ? 'event' : TRUE;
257:                 }
258:             } catch (\ReflectionException $e) {
259:             }
260:         }
261:         return $prop;
262:     }
263: 
264: 
265:     /**
266:      * Returns array of public (static, non-static and magic) methods.
267:      * @return array
268:      */
269:     private static function & getMethods($class)
270:     {
271:         if (!isset(self::$methods[$class])) {
272:             self::$methods[$class] = array_fill_keys(get_class_methods($class), 0) + self::getMagicMethods($class);
273:             if ($parent = get_parent_class($class)) {
274:                 self::$methods[$class] += self::getMethods($parent);
275:             }
276:         }
277:         return self::$methods[$class];
278:     }
279: 
280: 
281:     /**
282:      * Returns array of magic methods defined by annotation @method.
283:      * @return array
284:      */
285:     public static function getMagicMethods($class)
286:     {
287:         $rc = new \ReflectionClass($class);
288:         preg_match_all('~^
289:             [ \t*]*  @method  [ \t]+
290:             (?: [^\s(]+  [ \t]+ )?
291:             (set|get|is|add)  ([A-Z]\w*)  [ \t]*
292:             (?: \(  [ \t]* ([^)$\s]+)  )?
293:         ()~mx', $rc->getDocComment(), $matches, PREG_SET_ORDER);
294: 
295:         $methods = array();
296:         foreach ($matches as $m) {
297:             list(, $op, $prop, $type) = $m;
298:             $name = $op . $prop;
299:             $prop = strtolower($prop[0]) . substr($prop, 1) . ($op === 'add' ? 's' : '');
300:             if ($rc->hasProperty($prop) && ($rp = $rc->getProperty($prop)) && !$rp->isStatic()) {
301:                 $rp->setAccessible(TRUE);
302:                 if ($op === 'get' || $op === 'is') {
303:                     $type = NULL;
304:                     $op = 'get';
305:                 } elseif (!$type && preg_match('#@var[ \t]+(\S+)' . ($op === 'add' ? '\[\]#' : '#'), $rp->getDocComment(), $m)) {
306:                     $type = $m[1];
307:                 }
308:                 if ($rc->inNamespace() && preg_match('#^[A-Z]\w+(\[|\||\z)#', $type)) {
309:                     $type = $rc->getNamespaceName() . '\\' . $type;
310:                 }
311:                 $methods[$name] = array($op, $rp, $type);
312:             }
313:         }
314:         return $methods;
315:     }
316: 
317: 
318:     /**
319:      * Finds whether a variable is of expected type and do non-data-loss conversion.
320:      * @return bool
321:      * @internal
322:      */
323:     public static function checkType(& $val, $type)
324:     {
325:         if (strpos($type, '|') !== FALSE) {
326:             $found = NULL;
327:             foreach (explode('|', $type) as $type) {
328:                 $tmp = $val;
329:                 if (self::checkType($tmp, $type)) {
330:                     if ($val === $tmp) {
331:                         return TRUE;
332:                     }
333:                     $found[] = $tmp;
334:                 }
335:             }
336:             if ($found) {
337:                 $val = $found[0];
338:                 return TRUE;
339:             }
340:             return FALSE;
341: 
342:         } elseif (substr($type, -2) === '[]') {
343:             if (!is_array($val)) {
344:                 return FALSE;
345:             }
346:             $type = substr($type, 0, -2);
347:             $res = array();
348:             foreach ($val as $k => $v) {
349:                 if (!self::checkType($v, $type)) {
350:                     return FALSE;
351:                 }
352:                 $res[$k] = $v;
353:             }
354:             $val = $res;
355:             return TRUE;
356:         }
357: 
358:         switch (strtolower($type)) {
359:             case NULL:
360:             case 'mixed':
361:                 return TRUE;
362:             case 'bool':
363:             case 'boolean':
364:                 return ($val === NULL || is_scalar($val)) && settype($val, 'bool');
365:             case 'string':
366:                 return ($val === NULL || is_scalar($val) || (is_object($val) && method_exists($val, '__toString'))) && settype($val, 'string');
367:             case 'int':
368:             case 'integer':
369:                 return ($val === NULL || is_bool($val) || is_numeric($val)) && ((float) (int) $val === (float) $val) && settype($val, 'int');
370:             case 'float':
371:                 return ($val === NULL || is_bool($val) || is_numeric($val)) && settype($val, 'float');
372:             case 'scalar':
373:             case 'array':
374:             case 'object':
375:             case 'callable':
376:             case 'resource':
377:             case 'null':
378:                 return call_user_func("is_$type", $val);
379:             default:
380:                 return $val instanceof $type;
381:         }
382:     }
383: 
384: 
385:     /**
386:      * Adds a method to class.
387:      * @param  string
388:      * @param  string
389:      * @param  mixed   callable
390:      * @return void
391:      */
392:     public static function setExtensionMethod($class, $name, $callback)
393:     {
394:         $name = strtolower($name);
395:         self::$extMethods[$name][$class] = Callback::check($callback);
396:         self::$extMethods[$name][''] = NULL;
397:     }
398: 
399: 
400:     /**
401:      * Returns extension method.
402:      * @param  string
403:      * @param  string
404:      * @return mixed
405:      */
406:     public static function getExtensionMethod($class, $name)
407:     {
408:         $list = & self::$extMethods[strtolower($name)];
409:         $cache = & $list[''][$class];
410:         if (isset($cache)) {
411:             return $cache;
412:         }
413: 
414:         foreach (array($class) + class_parents($class) + class_implements($class) as $cl) {
415:             if (isset($list[$cl])) {
416:                 return $cache = $list[$cl];
417:             }
418:         }
419:         return $cache = FALSE;
420:     }
421: 
422: 
423:     /**
424:      * Returns extension methods.
425:      * @param  string
426:      * @return array
427:      */
428:     public static function getExtensionMethods($class)
429:     {
430:         $res = array();
431:         foreach (array_keys(self::$extMethods) as $name) {
432:             if ($cb = self::getExtensionMethod($class, $name)) {
433:                 $res[$name] = $cb;
434:             }
435:         }
436:         return $res;
437:     }
438: 
439: 
440:     /**
441:      * Finds the best suggestion (for 8-bit encoding).
442:      * @return string|NULL
443:      * @internal
444:      */
445:     public static function getSuggestion(array $items, $value)
446:     {
447:         $norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '', $value);
448:         $best = NULL;
449:         $min = (strlen($value) / 4 + 1) * 10 + .1;
450:         foreach (array_unique($items) as $item) {
451:             if ($item !== $value && (
452:                 ($len = levenshtein($item, $value, 10, 11, 10)) < $min
453:                 || ($len = levenshtein(preg_replace($re, '', $item), $norm, 10, 11, 10) + 20) < $min
454:             )) {
455:                 $min = $len;
456:                 $best = $item;
457:             }
458:         }
459:         return $best;
460:     }
461: 
462: 
463:     private static function parseFullDoc($class, $pattern)
464:     {
465:         $rc = new \ReflectionClass($class);
466:         do {
467:             $doc[] = $rc->getDocComment();
468:         } while ($rc = $rc->getParentClass());
469:         return preg_match_all($pattern, implode($doc), $m) ? $m[1] : array();
470:     }
471: 
472: }
473: 
Nette 2.3-20161221 API API documentation generated by ApiGen 2.8.0