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; 11 12use Nette\Utils\ObjectHelpers; 13 14 15/** 16 * Strict class for better experience. 17 * - 'did you mean' hints 18 * - access to undeclared members throws exceptions 19 * - support for @property annotations 20 * - support for calling event handlers stored in $onEvent via onEvent() 21 */ 22trait SmartObject 23{ 24 /** 25 * @return mixed 26 * @throws MemberAccessException 27 */ 28 public function __call(string $name, array $args) 29 { 30 $class = static::class; 31 32 if (ObjectHelpers::hasProperty($class, $name) === 'event') { // calling event handlers 33 $handlers = $this->$name ?? null; 34 if (is_iterable($handlers)) { 35 foreach ($handlers as $handler) { 36 $handler(...$args); 37 } 38 } elseif ($handlers !== null) { 39 throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . get_debug_type($handlers) . ' given.'); 40 } 41 42 return null; 43 } 44 45 ObjectHelpers::strictCall($class, $name); 46 } 47 48 49 /** 50 * @throws MemberAccessException 51 */ 52 public static function __callStatic(string $name, array $args) 53 { 54 ObjectHelpers::strictStaticCall(static::class, $name); 55 } 56 57 58 /** 59 * @return mixed 60 * @throws MemberAccessException if the property is not defined. 61 */ 62 public function &__get(string $name) 63 { 64 $class = static::class; 65 66 if ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property getter 67 if (!($prop & 0b0001)) { 68 throw new MemberAccessException("Cannot read a write-only property $class::\$$name."); 69 } 70 71 $m = ($prop & 0b0010 ? 'get' : 'is') . ucfirst($name); 72 if ($prop & 0b10000) { 73 $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call() 74 $loc = isset($trace['file'], $trace['line']) 75 ? " in $trace[file] on line $trace[line]" 76 : ''; 77 trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED); 78 } 79 80 if ($prop & 0b0100) { // return by reference 81 return $this->$m(); 82 } else { 83 $val = $this->$m(); 84 return $val; 85 } 86 } else { 87 ObjectHelpers::strictGet($class, $name); 88 } 89 } 90 91 92 /** 93 * @throws MemberAccessException if the property is not defined or is read-only 94 */ 95 public function __set(string $name, mixed $value): void 96 { 97 $class = static::class; 98 99 if (ObjectHelpers::hasProperty($class, $name)) { // unsetted property 100 $this->$name = $value; 101 102 } elseif ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property setter 103 if (!($prop & 0b1000)) { 104 throw new MemberAccessException("Cannot write to a read-only property $class::\$$name."); 105 } 106 107 $m = 'set' . ucfirst($name); 108 if ($prop & 0b10000) { 109 $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call() 110 $loc = isset($trace['file'], $trace['line']) 111 ? " in $trace[file] on line $trace[line]" 112 : ''; 113 trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED); 114 } 115 116 $this->$m($value); 117 118 } else { 119 ObjectHelpers::strictSet($class, $name); 120 } 121 } 122 123 124 /** 125 * @throws MemberAccessException 126 */ 127 public function __unset(string $name): void 128 { 129 $class = static::class; 130 if (!ObjectHelpers::hasProperty($class, $name)) { 131 throw new MemberAccessException("Cannot unset the property $class::\$$name."); 132 } 133 } 134 135 136 public function __isset(string $name): bool 137 { 138 return isset(ObjectHelpers::getMagicProperties(static::class)[$name]); 139 } 140} 141