friendship ended with social-app. php is my new best friend
1<?php 2/** 3 * Class SettingsContainerAbstract 4 * 5 * @created 28.08.2018 6 * @author Smiley <smiley@chillerlan.net> 7 * @copyright 2018 Smiley 8 * @license MIT 9 */ 10declare(strict_types=1); 11 12namespace chillerlan\Settings; 13 14use InvalidArgumentException, JsonException, ReflectionClass, ReflectionProperty; 15use function array_keys, get_object_vars, is_object, json_decode, json_encode, 16 json_last_error_msg, method_exists, property_exists, serialize, unserialize; 17use const JSON_THROW_ON_ERROR; 18 19abstract class SettingsContainerAbstract implements SettingsContainerInterface{ 20 21 /** 22 * SettingsContainerAbstract constructor. 23 * 24 * @phpstan-param array<string, mixed> $properties 25 */ 26 public function __construct(iterable|null $properties = null){ 27 28 if(!empty($properties)){ 29 $this->fromIterable($properties); 30 } 31 32 $this->construct(); 33 } 34 35 /** 36 * calls a method with trait name as replacement constructor for each used trait 37 * (remember pre-php5 classname constructors? yeah, basically this.) 38 */ 39 protected function construct():void{ 40 $traits = (new ReflectionClass($this))->getTraits(); 41 42 foreach($traits as $trait){ 43 $method = $trait->getShortName(); 44 45 if(method_exists($this, $method)){ 46 $this->{$method}(); 47 } 48 } 49 50 } 51 52 /** 53 * @inheritdoc 54 */ 55 public function __get(string $property):mixed{ 56 57 if(!property_exists($this, $property) || $this->isPrivate($property)){ 58 return null; 59 } 60 61 $method = 'get_'.$property; 62 63 if(method_exists($this, $method)){ 64 return $this->{$method}(); 65 } 66 67 return $this->{$property}; 68 } 69 70 /** 71 * @inheritdoc 72 */ 73 public function __set(string $property, mixed $value):void{ 74 75 if(!property_exists($this, $property) || $this->isPrivate($property)){ 76 return; 77 } 78 79 $method = 'set_'.$property; 80 81 if(method_exists($this, $method)){ 82 $this->{$method}($value); 83 84 return; 85 } 86 87 $this->{$property} = $value; 88 } 89 90 /** 91 * @inheritdoc 92 */ 93 public function __isset(string $property):bool{ 94 return isset($this->{$property}) && !$this->isPrivate($property); 95 } 96 97 /** 98 * @internal Checks if a property is private 99 */ 100 protected function isPrivate(string $property):bool{ 101 return (new ReflectionProperty($this, $property))->isPrivate(); 102 } 103 104 /** 105 * @inheritdoc 106 */ 107 public function __unset(string $property):void{ 108 109 if($this->__isset($property)){ 110 unset($this->{$property}); 111 } 112 113 } 114 115 /** 116 * @inheritdoc 117 */ 118 public function __toString():string{ 119 return $this->toJSON(); 120 } 121 122 /** 123 * @inheritdoc 124 */ 125 public function toArray():array{ 126 $properties = []; 127 128 foreach(array_keys(get_object_vars($this)) as $key){ 129 $properties[$key] = $this->__get($key); 130 } 131 132 return $properties; 133 } 134 135 /** 136 * @inheritdoc 137 */ 138 public function fromIterable(iterable $properties):static{ 139 140 foreach($properties as $key => $value){ 141 $this->__set($key, $value); 142 } 143 144 return $this; 145 } 146 147 /** 148 * @inheritdoc 149 */ 150 public function toJSON(int|null $jsonOptions = null):string{ 151 $json = json_encode($this, ($jsonOptions ?? 0)); 152 153 if($json === false){ 154 throw new JsonException(json_last_error_msg()); 155 } 156 157 return $json; 158 } 159 160 /** 161 * @inheritdoc 162 */ 163 public function fromJSON(string $json):static{ 164 /** @phpstan-var array<string, mixed> $data */ 165 $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); 166 167 return $this->fromIterable($data); 168 } 169 170 /** 171 * @inheritdoc 172 * @return array<string, mixed> 173 */ 174 public function jsonSerialize():array{ 175 return $this->toArray(); 176 } 177 178 /** 179 * Returns a serialized string representation of the object in its current state (except static/readonly properties) 180 * 181 * @inheritdoc 182 * @see \chillerlan\Settings\SettingsContainerInterface::toArray() 183 */ 184 public function serialize():string{ 185 return serialize($this); 186 } 187 188 /** 189 * Restores the data (except static/readonly properties) from the given serialized object to the current instance 190 * 191 * @inheritdoc 192 * @see \chillerlan\Settings\SettingsContainerInterface::fromIterable() 193 */ 194 public function unserialize(string $data):void{ 195 $obj = unserialize($data); 196 197 if($obj === false || !is_object($obj)){ 198 throw new InvalidArgumentException('The given serialized string is invalid'); 199 } 200 201 $reflection = new ReflectionClass($obj); 202 203 if(!$reflection->isInstance($this)){ 204 throw new InvalidArgumentException('The unserialized object does not match the class of this container'); 205 } 206 207 $properties = $reflection->getProperties(~(ReflectionProperty::IS_STATIC | ReflectionProperty::IS_READONLY)); 208 209 foreach($properties as $reflectionProperty){ 210 $this->{$reflectionProperty->name} = $reflectionProperty->getValue($obj); 211 } 212 213 } 214 215 /** 216 * Returns a serialized string representation of the object in its current state (except static/readonly properties) 217 * 218 * @inheritdoc 219 * @see \chillerlan\Settings\SettingsContainerInterface::toArray() 220 */ 221 public function __serialize():array{ 222 223 $properties = (new ReflectionClass($this)) 224 ->getProperties(~(ReflectionProperty::IS_STATIC | ReflectionProperty::IS_READONLY)) 225 ; 226 227 $data = []; 228 229 foreach($properties as $reflectionProperty){ 230 $data[$reflectionProperty->name] = $reflectionProperty->getValue($this); 231 } 232 233 return $data; 234 } 235 236 /** 237 * Restores the data from the given array to the current instance 238 * 239 * @inheritdoc 240 * @see \chillerlan\Settings\SettingsContainerInterface::fromIterable() 241 * 242 * @param array<string, mixed> $data 243 */ 244 public function __unserialize(array $data):void{ 245 246 foreach($data as $key => $value){ 247 $this->{$key} = $value; 248 } 249 250 } 251 252}