3v4l.org

run code in 200+ php & hhvm versions
Bugs & Features
<?php abstract class AbstractModel { const TYPE_STRING = 'string'; const TYPE_INT = 'int'; const TYPE_FLOAT = 'float'; const TYPE_BOOL = 'bool'; const TYPE_ARRAY = 'array'; const TYPE_DATE_TIME = 'date-time'; const TYPE_MODEL = 'model'; const TYPE_MODEL_ARRAY = 'model-array'; /** * Returns an array of key value pairs representing the properties exposed * by the model and their types * * @return array */ abstract public function getModelProperties(); /** * Get a list of properties that can store a null value * * @return array */ public function getNullableProperties() { return []; } private function functionToProperty($name, $type) { if (strrpos($name, $type) === 0) { $property = lcfirst(substr($name, strlen($type))); $modelProperties = $this->getModelProperties(); if (!isset($modelProperties[$property])) { throw new \Exception('Property not registered: ' . $property); } return $property; } return false; } /** * Serialize value into snapshot format * * @param mixed $value Value to serialize * @param string $type Type of value * * @return mixed */ protected static function snapshotSerialize($value, $type) { switch ($type) { case self::TYPE_STRING: if (null !== $value) { return (string) $value; } break; case self::TYPE_INT: if (null !== $value) { return (int) $value; } break; case self::TYPE_FLOAT: if (null !== $value) { return (float) $value; } break; case self::TYPE_BOOL: if (null !== $value) { return (bool) $value; } break; case self::TYPE_DATE_TIME: if ($value instanceof \DateTime) { return [ 'text' => $value->format('Y-m-d H:i:s'), 'zone' => $value->getTimezone()->getName() ]; } return null; case self::TYPE_MODEL: $snapshot = $value->toSnapshot(); $snapshot['_class'] = get_class($value); return $snapshot; case self::TYPE_MODEL_ARRAY: return array_map(function (AbstractModel $item) { return self::snapshotSerialize($item, self::TYPE_MODEL); }, $value); } return $value; } /** * Unserialize from snapshot format * * @param mixed $value Value to unserialize * @param string $type Type of value * * @return mixed */ protected static function snapshotUnserialize($value, $type) { if (null === $value) { return null; } switch ($type) { case self::TYPE_DATE_TIME: if (!is_array($value) || !isset($value['text'], $value['zone'])) { throw new \Exception('Invalid date time type in snapshot'); } return new \DateTime($value['text'], new \DateTimeZone($value['zone'])); case self::TYPE_MODEL: if (!isset($value['_class'])) { throw new \Exception('Invalid model type in snapshot'); } $class = $value['_class']; unset($value['_class']); return $class::fromSnapshot($value); case self::TYPE_MODEL_ARRAY: if (!is_array($value)) { throw new \Exception('Invalid model array type in snapshot'); } return array_map(function (array $item) { return self::snapshotUnserialize($item, self::TYPE_MODEL); }, $value); } return $value; } protected static function cast($value, $type) { switch ($type) { case self::TYPE_STRING: return (string) $value; case self::TYPE_INT: return (int) $value; case self::TYPE_FLOAT: return (float) $value; case self::TYPE_BOOL: return (bool) $value; case self::TYPE_ARRAY: return (array) $value; case self::TYPE_DATE_TIME: if (!$value instanceof \DateTime) { throw new \InvalidArgumentException('Expected instance of DateTime for date time type'); } return $value; case self::TYPE_MODEL: if (!$value instanceof AbstractModel) { throw \InvalidArgumentException('Instance of AbstractModel expected for model type'); } return $value; case self::TYPE_MODEL_ARRAY: if (!is_array($value)) { throw \InvalidArgumentException('Array expected for model array type'); } return array_map(function (AbstractModel $item) { return self::cast($item, self::TYPE_MODEL); }, $value); } throw new \Exception('Unknown type: ' . $type); } /** * Applies a property map to the object * * @param array $map A property map * * @return $this * * @throws \Exception If given unregistered property */ public function applyMap($map) { $modelProperties = $this->getModelProperties(); foreach ($map as $property => $value) { if (!isset($modelProperties[$property])) { continue; // throw new \Exception('Property not registered: ' . $property); } $type = $modelProperties[$property]; if (self::TYPE_STRING !== $type && is_string($value) && strlen($value) === 0) { $value = null; } if (null === $value && in_array($property, $this->getNullableProperties())) { $this->$property = null; } else { $this->$property = self::snapshotUnserialize($value, $type); } } return $this; } /** * Instantiates a model class from a snapshot * * @param array $snapshot The snapshot * * @return AbstractModel */ public static function fromSnapshot($snapshot) { $class = get_called_class(); $object = new $class; $object->applyMap($snapshot); return $object; } /** * Creates a snapshot of the model class * * @return array */ public function toSnapshot() { $modelProperties = $this->getModelProperties(); $snapshot = array(); foreach ($modelProperties as $property => $type) { $snapshot[$property] = self::snapshotSerialize($this->$property, $type); } return $snapshot; } public function __call($name, $arguments) { $modelProperties = $this->getModelProperties(); if(false !== ($property = $this->functionToProperty($name, 'is'))) { if (self::TYPE_BOOL !== $modelProperties[$property] ) { throw new \Exception('Can only use isser on boolean property'); } return $this->$property; } elseif (false !== ($property = $this->functionToProperty($name, 'set'))) { if (1 !== count($arguments)) { throw new \Exception('Setter must be called with one argument only'); } if (null === $arguments[0] && in_array($property, $this->getNullableProperties())) { $this->$property = null; } else { $this->$property = self::cast($arguments[0], $modelProperties[$property]); } return $this; } elseif (false !== ($property = $this->functionToProperty($name, 'get'))) { if (null === $this->$property && in_array($property, $this->getNullableProperties())) { return null; } else { return $this->$property; } } trigger_error('Call to undefined method ' . get_called_class() . ':' . $name); } } class A extends AbstractModel { private $bool; public function getModelProperties() { return ['bool' => self::TYPE_BOOL]; } } $a = A::fromSnapshot(['bool' => 'true']); var_dump($a); var_dump($a->getBool());
Output for 7.0.0 - 7.2.0
Fatal error: Uncaught Error: Cannot access private property A::$bool in /in/EpTDM:225 Stack trace: #0 /in/EpTDM(244): AbstractModel->applyMap(Array) #1 /in/EpTDM(310): AbstractModel::fromSnapshot(Array) #2 {main} thrown in /in/EpTDM on line 225
Process exited with code 255.
Output for 5.5.0 - 5.6.28
Fatal error: Cannot access private property A::$bool in /in/EpTDM on line 225
Process exited with code 255.