@ 2015-11-04T13:55:41Z <?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());
Enable javascript to submit You have javascript disabled. You will not be able to edit any code.
Output for 7.0.0 - 7.0.20 , 7.1.0 - 7.1.20 , 7.2.0 - 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 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 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
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.5.38 , 5.6.0 - 5.6.28 Fatal error: Cannot access private property A::$bool in /in/EpTDM on line 225
Process exited with code 255 . preferences:dark mode live preview
171.23 ms | 402 KiB | 224 Q