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
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

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

Interfaces

  • IHtmlString

Exceptions

  • AssertionException
  • ImageException
  • JsonException
  • RegexpException
  • TokenizerException
  • UnknownImageFileException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  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:     use Nette\StaticClass;
 20: 
 21:     /** @var array [name => [type => callback]] used by extension methods */
 22:     private static $extMethods = [];
 23: 
 24: 
 25:     /********************* strictness ****************d*g**/
 26: 
 27: 
 28:     /**
 29:      * @throws MemberAccessException
 30:      */
 31:     public static function strictGet($class, $name)
 32:     {
 33:         $rc = new \ReflectionClass($class);
 34:         $hint = self::getSuggestion(array_merge(
 35:             array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
 36:             self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
 37:         ), $name);
 38:         throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
 39:     }
 40: 
 41: 
 42:     /**
 43:      * @throws MemberAccessException
 44:      */
 45:     public static function strictSet($class, $name)
 46:     {
 47:         $rc = new \ReflectionClass($class);
 48:         $hint = self::getSuggestion(array_merge(
 49:             array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$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:      * @throws MemberAccessException
 58:      */
 59:     public static function strictCall($class, $method, $additionalMethods = [])
 60:     {
 61:         $hint = self::getSuggestion(array_merge(
 62:             get_class_methods($class),
 63:             self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'),
 64:             $additionalMethods
 65:         ), $method);
 66: 
 67:         if (method_exists($class, $method)) { // called parent::$method()
 68:             $class = 'parent';
 69:         }
 70:         throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
 71:     }
 72: 
 73: 
 74:     /**
 75:      * @throws MemberAccessException
 76:      */
 77:     public static function strictStaticCall($class, $method)
 78:     {
 79:         $hint = self::getSuggestion(
 80:             array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }),
 81:             $method
 82:         );
 83:         throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
 84:     }
 85: 
 86: 
 87:     /********************* Nette\Object ****************d*g**/
 88: 
 89: 
 90:     /**
 91:      * __call() implementation.
 92:      * @param  object
 93:      * @param  string
 94:      * @param  array
 95:      * @return mixed
 96:      * @throws MemberAccessException
 97:      */
 98:     public static function call($_this, $name, $args)
 99:     {
100:         $class = get_class($_this);
101:         $isProp = self::hasProperty($class, $name);
102: 
103:         if ($name === '') {
104:             throw new MemberAccessException("Call to class '$class' method without name.");
105: 
106:         } elseif ($isProp === 'event') { // calling event handlers
107:             if (is_array($_this->$name) || $_this->$name instanceof \Traversable) {
108:                 foreach ($_this->$name as $handler) {
109:                     Callback::invokeArgs($handler, $args);
110:                 }
111:             } elseif ($_this->$name !== null) {
112:                 throw new Nette\UnexpectedValueException("Property $class::$$name must be array or null, " . gettype($_this->$name) . ' given.');
113:             }
114: 
115:         } elseif ($isProp && $_this->$name instanceof \Closure) { // closure in property
116:             return call_user_func_array($_this->$name, $args);
117: 
118:         } elseif (($methods = &self::getMethods($class)) && isset($methods[$name]) && is_array($methods[$name])) { // magic @methods
119:             list($op, $rp, $type) = $methods[$name];
120:             if (count($args) !== ($op === 'get' ? 0 : 1)) {
121:                 throw new Nette\InvalidArgumentException("$class::$name() expects " . ($op === 'get' ? 'no' : '1') . ' argument, ' . count($args) . ' given.');
122: 
123:             } elseif ($type && $args && !self::checkType($args[0], $type)) {
124:                 throw new Nette\InvalidArgumentException("Argument passed to $class::$name() must be $type, " . gettype($args[0]) . ' given.');
125:             }
126: 
127:             if ($op === 'get') {
128:                 return $rp->getValue($_this);
129:             } elseif ($op === 'set') {
130:                 $rp->setValue($_this, $args[0]);
131:             } elseif ($op === 'add') {
132:                 $val = $rp->getValue($_this);
133:                 $val[] = $args[0];
134:                 $rp->setValue($_this, $val);
135:             }
136:             return $_this;
137: 
138:         } elseif ($cb = self::getExtensionMethod($class, $name)) { // extension methods
139:             return Callback::invoke($cb, $_this, ...$args);
140: 
141:         } else {
142:             self::strictCall($class, $name, array_keys(self::getExtensionMethods($class)));
143:         }
144:     }
145: 
146: 
147:     /**
148:      * __callStatic() implementation.
149:      * @param  string
150:      * @param  string
151:      * @param  array
152:      * @return void
153:      * @throws MemberAccessException
154:      */
155:     public static function callStatic($class, $method, $args)
156:     {
157:         self::strictStaticCall($class, $method);
158:     }
159: 
160: 
161:     /**
162:      * __get() implementation.
163:      * @param  object
164:      * @param  string  property name
165:      * @return mixed   property value
166:      * @throws MemberAccessException if the property is not defined.
167:      */
168:     public static function &get($_this, $name)
169:     {
170:         $class = get_class($_this);
171:         $uname = ucfirst($name);
172:         $methods = &self::getMethods($class);
173: 
174:         if ($name === '') {
175:             throw new MemberAccessException("Cannot read a class '$class' property without name.");
176: 
177:         } elseif (isset($methods[$m = 'get' . $uname]) || isset($methods[$m = 'is' . $uname])) { // property getter
178:             if ($methods[$m] === 0) {
179:                 $methods[$m] = (new \ReflectionMethod($class, $m))->returnsReference();
180:             }
181:             if ($methods[$m] === true) {
182:                 return $_this->$m();
183:             } else {
184:                 $val = $_this->$m();
185:                 return $val;
186:             }
187: 
188:         } elseif (isset($methods[$name])) { // public method as closure getter
189:             if (preg_match('#^(is|get|has)([A-Z]|$)#', $name) && !(new \ReflectionMethod($class, $name))->getNumberOfRequiredParameters()) {
190:                 trigger_error("Did you forget parentheses after $name" . self::getSource() . '?', E_USER_WARNING);
191:             }
192:             $val = Callback::closure($_this, $name);
193:             return $val;
194: 
195:         } elseif (isset($methods['set' . $uname])) { // property getter
196:             throw new MemberAccessException("Cannot read a write-only property $class::\$$name.");
197: 
198:         } else {
199:             self::strictGet($class, $name);
200:         }
201:     }
202: 
203: 
204:     /**
205:      * __set() implementation.
206:      * @param  object
207:      * @param  string  property name
208:      * @param  mixed   property value
209:      * @return void
210:      * @throws MemberAccessException if the property is not defined or is read-only
211:      */
212:     public static function set($_this, $name, $value)
213:     {
214:         $class = get_class($_this);
215:         $uname = ucfirst($name);
216:         $methods = &self::getMethods($class);
217: 
218:         if ($name === '') {
219:             throw new MemberAccessException("Cannot write to a class '$class' property without name.");
220: 
221:         } elseif (self::hasProperty($class, $name)) { // unsetted property
222:             $_this->$name = $value;
223: 
224:         } elseif (isset($methods[$m = 'set' . $uname])) { // property setter
225:             $_this->$m($value);
226: 
227:         } elseif (isset($methods['get' . $uname]) || isset($methods['is' . $uname])) { // property setter
228:             throw new MemberAccessException("Cannot write to a read-only property $class::\$$name.");
229: 
230:         } else {
231:             self::strictSet($class, $name);
232:         }
233:     }
234: 
235: 
236:     /**
237:      * __unset() implementation.
238:      * @param  object
239:      * @param  string  property name
240:      * @return void
241:      * @throws MemberAccessException
242:      */
243:     public static function remove($_this, $name)
244:     {
245:         $class = get_class($_this);
246:         if (!self::hasProperty($class, $name)) {
247:             throw new MemberAccessException("Cannot unset the property $class::\$$name.");
248:         }
249:     }
250: 
251: 
252:     /**
253:      * __isset() implementation.
254:      * @param  object
255:      * @param  string  property name
256:      * @return bool
257:      */
258:     public static function has($_this, $name)
259:     {
260:         $name = ucfirst($name);
261:         $methods = &self::getMethods(get_class($_this));
262:         return $name !== '' && (isset($methods['get' . $name]) || isset($methods['is' . $name]));
263:     }
264: 
265: 
266:     /********************* magic @properties ****************d*g**/
267: 
268: 
269:     /**
270:      * Returns array of magic properties defined by annotation @property.
271:      * @return array of [name => bit mask]
272:      */
273:     public static function getMagicProperties($class)
274:     {
275:         static $cache;
276:         $props = &$cache[$class];
277:         if ($props !== null) {
278:             return $props;
279:         }
280: 
281:         $rc = new \ReflectionClass($class);
282:         preg_match_all(
283:             '~^  [ \t*]*  @property(|-read|-write)  [ \t]+  [^\s$]+  [ \t]+  \$  (\w+)  ()~mx',
284:             (string) $rc->getDocComment(), $matches, PREG_SET_ORDER
285:         );
286: 
287:         $props = [];
288:         foreach ($matches as list(, $type, $name)) {
289:             $uname = ucfirst($name);
290:             $write = $type !== '-read'
291:                 && $rc->hasMethod($nm = 'set' . $uname)
292:                 && ($rm = $rc->getMethod($nm)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic();
293:             $read = $type !== '-write'
294:                 && ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname))
295:                 && ($rm = $rc->getMethod($nm)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic();
296: 
297:             if ($read || $write) {
298:                 $props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3;
299:             }
300:         }
301: 
302:         foreach ($rc->getTraits() as $trait) {
303:             $props += self::getMagicProperties($trait->getName());
304:         }
305: 
306:         if ($parent = get_parent_class($class)) {
307:             $props += self::getMagicProperties($parent);
308:         }
309:         return $props;
310:     }
311: 
312: 
313:     /** @internal */
314:     public static function getMagicProperty($class, $name)
315:     {
316:         $props = self::getMagicProperties($class);
317:         return isset($props[$name]) ? $props[$name] : null;
318:     }
319: 
320: 
321:     /********************* magic @methods ****************d*g**/
322: 
323: 
324:     /**
325:      * Returns array of magic methods defined by annotation @method.
326:      * @return array
327:      */
328:     public static function getMagicMethods($class)
329:     {
330:         $rc = new \ReflectionClass($class);
331:         preg_match_all('~^
332:             [ \t*]*  @method  [ \t]+
333:             (?: [^\s(]+  [ \t]+ )?
334:             (set|get|is|add)  ([A-Z]\w*)
335:             (?: ([ \t]* \()  [ \t]* ([^)$\s]*)  )?
336:         ()~mx', (string) $rc->getDocComment(), $matches, PREG_SET_ORDER);
337: 
338:         $methods = [];
339:         foreach ($matches as list(, $op, $prop, $bracket, $type)) {
340:             if ($bracket !== '(') {
341:                 trigger_error("Bracket must be immediately after @method $op$prop() in class $class.", E_USER_WARNING);
342:             }
343:             $name = $op . $prop;
344:             $prop = strtolower($prop[0]) . substr($prop, 1) . ($op === 'add' ? 's' : '');
345:             if ($rc->hasProperty($prop) && ($rp = $rc->getProperty($prop)) && !$rp->isStatic()) {
346:                 $rp->setAccessible(true);
347:                 if ($op === 'get' || $op === 'is') {
348:                     $type = null;
349:                     $op = 'get';
350:                 } elseif (!$type && preg_match('#@var[ \t]+(\S+)' . ($op === 'add' ? '\[\]#' : '#'), (string) $rp->getDocComment(), $m)) {
351:                     $type = $m[1];
352:                 }
353:                 if ($rc->inNamespace() && preg_match('#^[A-Z]\w+(\[|\||\z)#', (string) $type)) {
354:                     $type = $rc->getNamespaceName() . '\\' . $type;
355:                 }
356:                 $methods[$name] = [$op, $rp, $type];
357:             }
358:         }
359:         return $methods;
360:     }
361: 
362: 
363:     /**
364:      * Finds whether a variable is of expected type and do non-data-loss conversion.
365:      * @return bool
366:      * @internal
367:      */
368:     public static function checkType(&$val, $type)
369:     {
370:         if (strpos($type, '|') !== false) {
371:             $found = null;
372:             foreach (explode('|', $type) as $type) {
373:                 $tmp = $val;
374:                 if (self::checkType($tmp, $type)) {
375:                     if ($val === $tmp) {
376:                         return true;
377:                     }
378:                     $found[] = $tmp;
379:                 }
380:             }
381:             if ($found) {
382:                 $val = $found[0];
383:                 return true;
384:             }
385:             return false;
386: 
387:         } elseif (substr($type, -2) === '[]') {
388:             if (!is_array($val)) {
389:                 return false;
390:             }
391:             $type = substr($type, 0, -2);
392:             $res = [];
393:             foreach ($val as $k => $v) {
394:                 if (!self::checkType($v, $type)) {
395:                     return false;
396:                 }
397:                 $res[$k] = $v;
398:             }
399:             $val = $res;
400:             return true;
401:         }
402: 
403:         switch (strtolower($type)) {
404:             case null:
405:             case 'mixed':
406:                 return true;
407:             case 'bool':
408:             case 'boolean':
409:                 return ($val === null || is_scalar($val)) && settype($val, 'bool');
410:             case 'string':
411:                 return ($val === null || is_scalar($val) || (is_object($val) && method_exists($val, '__toString'))) && settype($val, 'string');
412:             case 'int':
413:             case 'integer':
414:                 return ($val === null || is_bool($val) || is_numeric($val)) && ((float) (int) $val === (float) $val) && settype($val, 'int');
415:             case 'float':
416:                 return ($val === null || is_bool($val) || is_numeric($val)) && settype($val, 'float');
417:             case 'scalar':
418:             case 'array':
419:             case 'object':
420:             case 'callable':
421:             case 'resource':
422:             case 'null':
423:                 return call_user_func("is_$type", $val);
424:             default:
425:                 return $val instanceof $type;
426:         }
427:     }
428: 
429: 
430:     /********************* extension methods ****************d*g**/
431: 
432: 
433:     /**
434:      * Adds a method to class.
435:      * @param  string
436:      * @param  string
437:      * @param  mixed   callable
438:      * @return void
439:      */
440:     public static function setExtensionMethod($class, $name, $callback)
441:     {
442:         $name = strtolower($name);
443:         self::$extMethods[$name][$class] = Callback::check($callback);
444:         self::$extMethods[$name][''] = null;
445:     }
446: 
447: 
448:     /**
449:      * Returns extension method.
450:      * @param  string
451:      * @param  string
452:      * @return mixed
453:      */
454:     public static function getExtensionMethod($class, $name)
455:     {
456:         $list = &self::$extMethods[strtolower($name)];
457:         $cache = &$list[''][$class];
458:         if (isset($cache)) {
459:             return $cache;
460:         }
461: 
462:         foreach ([$class] + class_parents($class) + class_implements($class) as $cl) {
463:             if (isset($list[$cl])) {
464:                 return $cache = $list[$cl];
465:             }
466:         }
467:         return $cache = false;
468:     }
469: 
470: 
471:     /**
472:      * Returns extension methods.
473:      * @param  string
474:      * @return array
475:      */
476:     public static function getExtensionMethods($class)
477:     {
478:         $res = [];
479:         foreach (array_keys(self::$extMethods) as $name) {
480:             if ($cb = self::getExtensionMethod($class, $name)) {
481:                 $res[$name] = $cb;
482:             }
483:         }
484:         return $res;
485:     }
486: 
487: 
488:     /********************* utilities ****************d*g**/
489: 
490: 
491:     /**
492:      * Finds the best suggestion (for 8-bit encoding).
493:      * @return string|null
494:      * @internal
495:      */
496:     public static function getSuggestion(array $possibilities, $value)
497:     {
498:         $norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '', $value);
499:         $best = null;
500:         $min = (strlen($value) / 4 + 1) * 10 + .1;
501:         foreach (array_unique($possibilities, SORT_REGULAR) as $item) {
502:             $item = $item instanceof \Reflector ? $item->getName() : $item;
503:             if ($item !== $value && (
504:                 ($len = levenshtein($item, $value, 10, 11, 10)) < $min
505:                 || ($len = levenshtein(preg_replace($re, '', $item), $norm, 10, 11, 10) + 20) < $min
506:             )) {
507:                 $min = $len;
508:                 $best = $item;
509:             }
510:         }
511:         return $best;
512:     }
513: 
514: 
515:     private static function parseFullDoc(\ReflectionClass $rc, $pattern)
516:     {
517:         do {
518:             $doc[] = $rc->getDocComment();
519:             $traits = $rc->getTraits();
520:             while ($trait = array_pop($traits)) {
521:                 $doc[] = $trait->getDocComment();
522:                 $traits += $trait->getTraits();
523:             }
524:         } while ($rc = $rc->getParentClass());
525:         return preg_match_all($pattern, implode($doc), $m) ? $m[1] : [];
526:     }
527: 
528: 
529:     /**
530:      * Checks if the public non-static property exists.
531:      * @return bool|'event'
532:      * @internal
533:      */
534:     public static function hasProperty($class, $name)
535:     {
536:         static $cache;
537:         $prop = &$cache[$class][$name];
538:         if ($prop === null) {
539:             $prop = false;
540:             try {
541:                 $rp = new \ReflectionProperty($class, $name);
542:                 if ($rp->isPublic() && !$rp->isStatic()) {
543:                     $prop = $name >= 'onA' && $name < 'on_' ? 'event' : true;
544:                 }
545:             } catch (\ReflectionException $e) {
546:             }
547:         }
548:         return $prop;
549:     }
550: 
551: 
552:     /**
553:      * Returns array of public (static, non-static and magic) methods.
554:      * @return array
555:      * @internal
556:      */
557:     public static function &getMethods($class)
558:     {
559:         static $cache;
560:         if (!isset($cache[$class])) {
561:             $cache[$class] = array_fill_keys(get_class_methods($class), 0) + self::getMagicMethods($class);
562:             if ($parent = get_parent_class($class)) {
563:                 $cache[$class] += self::getMethods($parent);
564:             }
565:         }
566:         return $cache[$class];
567:     }
568: 
569: 
570:     /** @internal */
571:     public static function getSource()
572:     {
573:         foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) {
574:             if (isset($item['file']) && dirname($item['file']) !== __DIR__) {
575:                 return " in $item[file]:$item[line]";
576:             }
577:         }
578:     }
579: }
580: 
Nette 2.4-20170829 API API documentation generated by ApiGen 2.8.0