3v4l.org

run code in 300+ PHP versions simultaneously
<?php /** * TODO: Fix shitty doc. * * Provides type assertions for public object properties. All public properties are affected by this library. The * following assertions are provided: * * - Read-only property: the property can be accessed by everything, but only the object itself can modify it. This is * the default behavior af all public properties, unless configured otherwise. * - Guarded property: the property can be set by everything, but the value must pass a custom check for type/format, * provided by the object. * * In both cases the checks only apply for external callers (outside the object). Public properties can't be unset * (they can still be set to null, however). The trait also protects against dynamic properties being set on the object. */ trait PropAssert { // TODO: Move out all method bodies to another class, so code is loaded only if uses (i.e. with assertions enabled)? protected $__propAssert = []; /** * Initializes PropAssert. This method should be called once for each object, preferably at construction time. * * @return bool Always returns true, so it can be comfortably invoked in an assert statement. */ protected function initPropAssert(): bool { $refl = new \ReflectionClass(get_class($this)); $propList = $refl->getProperties(\ReflectionProperty::IS_PUBLIC); foreach ($propList as $prop) { $name = $prop->name; $this->__propAssert[$name] = [ 'guard' => null, 'value' => $this->{$name}, ]; unset($this->{$name}); } return true; } /** * Configured how a given public property is handled. By default, public properties are read-only. With this * method, * they can be set as writable, with a "guard" function, that checks the type and format of the value being set. * * @param string $name A public property name (the property should already be declared as public). * @param \Closure|null $guard A closure that guards the property while being set, or null to make the property * read-only (all public properties are read-only by default). If provided, the closure * should be in format ($value) => bool|string. The guard accepts a value about to be * set for the given property, and returns true if the value is valid, and false or a * string error message (for ex. "a string of length 6-32 chars"), if the value is * invalid. * @return bool Always returns true, so it can be comfortably invoked in an assert statement. * @throws \Error On bad input. */ protected function setPropAssert($name, ?\Closure $guard): bool { $this->__propAssertRequireDefined($name); $this->__propAssert[$name]['guard'] = $guard; return true; } // Internal method, don't invoke. public function __get($name) { $this->__propAssertRequireDefined($name); $isInternal = $this->__propAssertIsInternalCaller(); $prop = $this->__propAssert[$name]; return $this->__propAssert[$name]['value']; } // Internal method, don't invoke. public function __set($name, $value) { $this->__propAssertRequireDefined($name); $isInternal = $this->__propAssertIsInternalCaller(); $guard = $this->__propAssert[$name]['guard']; if (!$isInternal && !$guard) { throw new \TypeError('Cannot set read-only property ' . get_class($this) . '::$' . $name); } $result = $guard($value); if ($result === true) { $this->__propAssert[$name]['value'] = $value; } else { $details = is_string($result) ? ', expecting ' . $result : ''; throw new \TypeError('Invalid value for property ' . get_class($this) . '::$' . $name . $details); } } // Internal method, don't invoke. public function __isset($name) { $this->__propAssertRequireDefined($name); return $this->__propAssert[$name]['value'] === null; } // Internal method, don't invoke. public function __unset($name) { $this->__propAssertRequireDefined($name); throw new \TypeError('Cannot unset property ' . get_class($this) . '::$' . $name); } // Internal method, don't invoke. private function __propAssertRequireDefined($name) { if (!isset($this->__propAssert[$name])) { throw new \TypeError('Undefined property ' . get_class($this) . '::$' . $name); } } // Internal method, don't invoke. private function __propAssertIsInternalCaller() { // TODO: Relax this check, so static methods of the class and cross-object calls can skip checks? $object = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['object'] ?? null; return $this === $object; } } // -------------------------------------------------------------------------------------------------------------------- class Foo { use PropAssert; public $a = 10, $b = 20; function __construct() { $this->initPropAssert(); $this->setPropAssert('b', function ($v) { if (!is_int($v)) return 'an integer, you moran'; return true; }); } } $foo = new Foo(); echo $foo->a . PHP_EOL; echo $foo->b . PHP_EOL; $foo->b = 123; echo $foo->b . PHP_EOL; try { $foo->b = 'moran'; } catch (\Throwable $e) { echo $e->getMessage() . PHP_EOL; } try { $foo->a = 100; } catch (\Throwable $e) { echo $e->getMessage() . PHP_EOL; }
Output for 7.1.0 - 7.1.20, 7.2.6 - 7.2.33, 7.3.16 - 7.3.33, 7.4.0 - 7.4.33, 8.0.0 - 8.0.30, 8.1.0 - 8.1.28, 8.2.0 - 8.2.18, 8.3.0 - 8.3.4, 8.3.6
10 20 123 Invalid value for property Foo::$b, expecting an integer, you moran Cannot set read-only property Foo::$a
Output for 8.3.5
Warning: PHP Startup: Unable to load dynamic library 'sodium.so' (tried: /usr/lib/php/8.3.5/modules/sodium.so (libsodium.so.23: cannot open shared object file: No such file or directory), /usr/lib/php/8.3.5/modules/sodium.so.so (/usr/lib/php/8.3.5/modules/sodium.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0 10 20 123 Invalid value for property Foo::$b, expecting an integer, you moran Cannot set read-only property Foo::$a
Output for 7.0.0 - 7.0.20
Parse error: syntax error, unexpected '?', expecting variable (T_VARIABLE) in /in/9YFRE on line 57
Process exited with code 255.

preferences:
146.18 ms | 401 KiB | 171 Q