I have an object tree like the following, which I need to serialize and store on the filesystem. I need the full hierarchy with all class properties and later I will unserialize and restore the class hierarchy.
class X implements \Serializable {
private $x1;
public function serialize() {
return serialize(get_class_vars(get_class($this)));
}
public function unserialize($data) {
$values = unserialize($data);
foreach ($values as $key => $value) {
$this->$key = $value;
}
}
}
class A implements \Serializable {
private $a1;
private $a2;
// type of a3 is class X!
protected $a3;
public function serialize() {
return serialize(get_class_vars(get_class($this)));
}
public function unserialize($data) {
$values = unserialize($data);
foreach ($values as $key => $value) {
$this->$key = $value;
}
}
}
class B extends A implements \Serializable {
private $b1;
private $b2;
public function serialize() {
// $base = parent::serialize();
return serialize(get_class_vars(get_class($this)));
}
public function unserialize($data) {
$values = unserialize($data);
foreach ($values as $key => $value) {
$this->$key = $value;
}
}
}
class C extends A implements \Serializable {
private $c1;
private $c2;
public function serialize() {
// $base = parent::serialize();
return serialize(get_class_vars(get_class($this)));
}
public function unserialize($data) {
$values = unserialize($data);
foreach ($values as $key => $value) {
$this->$key = $value;
}
}
}
The subclasses can serialize itself, but for the base class I don't know, how I can combine the serialized data. Furthermore I get serialized data from the filesystem, but I don't know, which subclass I will get. Does PHP's unserialize() create the right class instance? It should also initialize the base class A.
How can I solve that?
Maybe I can use the var_dump() output, but how I can store it into a variable?
This is how I would recommend serializing objects:
class Color implements \Serializable
{
private $Name;
private $Type;
public function __construct(string $Name, int $Type)
{
$this->Name = $Name;
$this->Type = $Type;
}
public function serialize()
{
$Props['Name'] = $this->Name;
$Props['Type'] = $this->Type;
return serialize($Props);
}
public function unserialize($Data)
{
list($this->Name, $this->Type) = unserialize($Data);
}
}
class Blue extends Color
{
private $Intensity;
public function __construct()
{
parent::__construct('Blue', 10);
$this->Intensity = 90;
}
public function serialize()
{
$Props['parent'] = parent::serialize();
$Props['Intensity'] = $this->Intensity;
return serialize($Props);
}
public function unserialize($Data)
{
$Obj = unserialize($Data);
parent::unserialize($Obj['parent']);
$this->Intensity = $Obj['Intensity'];
}
}
Whichever object you pass in to the serialize() function is the object you will get back (as a string) to unserialize(). If you go your route, then you can implement the serialize()/unserialize() functions inside a trait and get_object_vars() will work properly for private variables.
I have implemented serialize() and unserialize() in every affected class like this:
public function serialize() {
$res = array();
$reflect = new \ReflectionClass(__CLASS__);
$propList = $reflect->getProperties();
foreach($propList as $prop) {
if ($prop->class != __CLASS__) {
continue; // visible properties of base clases
}
$name = $prop->name;
$res[$name . ":" . __CLASS__] = serialize($this->$name);
}
if (method_exists(get_parent_class(__CLASS__), "serialize")) {
$base = unserialize(parent::serialize());
$res = array_merge($res, $base);
}
return serialize($res);
}
public function unserialize($data) {
$values = unserialize($data);
foreach ($values as $key => $value) {
// key contains propertyName:className
$prop = explode(":", $key);
if ($prop[1] != __CLASS__) {
continue;
}
$this->$prop[0] = unserialize($value);
}
// call base class
if (method_exists(get_parent_class(__CLASS__), "unserialize")) {
parent::unserialize($data);
}
}
Maybe there is a solution to add this functionality to a base class to prevent code copies. It should work with simple properties, arrays and objects also for large object trees with multiple levels of parent classes.
Related
I've been doing a project in PHP for the last few hours and I have encountered into a problem.
The problem is I don't know how to access private variables in a class and I can't find it online.
Example:
<?php
class Example{
private $age;
public function __construct() {
$age = 14;
$this->checkAge();
}
private function checkAge() {
if($this->$age > 12)
echo "welcome!";
}
}
$boy = new Example();
?>
As far as I know, I should be able to access the variable with $this->$age but it isn't working.
Thank you.
EDIT: Got it working with help of the awesome stackoverflooooooooow community, this is how a working one looks.
<?php
class Example{
private $age;
public function __construct() {
$this->age = 14;
$this->checkAge();
}
private function checkAge() {
if($this->age > 12)
echo "welcome!";
}
}
$boy = new Example();
?>
Look at this approach.
first: create Entity that stores and retrieves data inside of private $attributes array, and with magic __set(), __get() You can also do like: $object->variable = 123
second: extend Entity with Human class and add some function specific to child class (for example hasValidAge()):
<?php
class Entity {
private $attributes;
public function __construct($attributes = []) {
$this->setAttributes($attributes);
}
public function setAttribute($key, $value) {
$this->attributes[$key] = $value;
return $this;
}
public function setAttributes($attributes = []) {
foreach($attributes AS $key => $value) {
$this->setAttribute($key, $value);
}
}
public function getAttribute($key, $fallback = null) {
return (isset($this->attributes[$key]))?
$this->attributes[$key] : $fallback;
}
public function __get($key) {
return $this->getAttribute($key);
}
public function __set($key, $value) {
$this->setAttribute($key, $value);
}
}
class Human extends Entity {
public function __construct($attributes = []) {
$this->setAttributes($attributes);
$this->checkAge();
}
public function hasValidAge() {
return ($this->getAttribute('age') > 12)? true : false;
}
}
$boy = new Human(['name' => 'Mark', 'age' => 14]);
if($boy->hasValidAge()) {
echo "Welcome ".$boy->name."!";
}
?>
p.s. I've removed echo "Welcome!" part from constructor because it's not cool to do echo from model object, in our example Human is model of Entity.
I am trying to store an array and manipulate that array using a custom class that extends ArrayObject.
class MyArrayObject extends ArrayObject {
protected $data = array();
public function offsetGet($name) {
return $this->data[$name];
}
public function offsetSet($name, $value) {
$this->data[$name] = $value;
}
public function offsetExists($name) {
return isset($this->data[$name]);
}
public function offsetUnset($name) {
unset($this->data[$name]);
}
}
The problem is if I do this:
$foo = new MyArrayObject();
$foo['blah'] = array('name' => 'bob');
$foo['blah']['name'] = 'fred';
echo $foo['blah']['name'];
The output is bob and not fred. Is there any way I can get this to work without changing the 4 lines above?
This is a known behaviour of ArrayAccess ("PHP Notice: Indirect modification of overloaded element of MyArrayObject has no effect" ...).
http://php.net/manual/en/class.arrayaccess.php
Implement this in MyArrayObject:
public function offsetSet($offset, $data) {
if (is_array($data)) $data = new self($data);
if ($offset === null) {
$this->data[] = $data;
} else {
$this->data[$offset] = $data;
}
}
I need to create an immutable class which is simply a member field container. I want its fields to be instantiated once in its constructor (the values should be given as parameters to the constructor). I want the fields to be public but immutable. I could have done it with Java using the final keyword before each field. How is it done in PHP?
You should use __set and __get magic methods and declare that property as protected or private:
/**
* #property-read string $value
*/
class Example
{
private $value;
public function __construct()
{
$this->value = "test";
}
public function __get($key)
{
if (property_exists($this, $key)) {
return $this->{$key};
} else {
return null; // or throw an exception
}
}
public function __set($key, $value)
{
return; // or throw an exception
}
}
Example:
$example = new Example();
var_dump($example->value);
$example->value = "invalid";
var_dump($example->value);
Outputs:
string(4) "test"
string(4) "test"
#property-read should help your IDE acknowledge existence of this magic property.
You could use the __set() magic method and throw an exception when someone tries to set a property directly.
class ClassName {
public function __set($key, $value) {
throw new Exception('Can't modify property directly.');
}
}
However, this would prevent modification of properties regardless of whether they're public or not.
magic methods
so you can do better - if you have a dinamyc count of fields
class ClassName {
private $fields = array();
// use class : $cl = new ClassName(array('f'=>2,'field_4'=>5,''12));
// echo $cl->field_4; echo $cl->f;
public function __construct($data= array())
{
if (!is_array($data) || !count($data)) throw new Exception('Not enough args')
foreach ($data as $key=>$val)
{
if (is_numeric($key))
$this->fields['field_'.$key] = $val;
else
$this->fields[$key] = $val;
}
}
/* in this case you can use this class like $cl = new ClassName(12,14,13,15,12); echo $cl->field_1;
public function __construct()
{
$ata = funcs_get_args();
if (!count($data)) throw new Exception('Not enough args')
foreach ($data as $key=>$val)
{
if (is_numeric($key))
$this->fields['field_'.$key] = $val;
else
$this->fields[$key] = $val;
}
}
*/
public function __get($var) {
if (isset($this->fields[$var]))
return $this->fields[$var];
return false;
//or throw new Exception ('Undeclared property');
}
}
<?php
declare(strict_types=1);
final class Immutable
{
/** #var string */
private $value;
public static function withValue(string $value): self
{
return new self($value);
}
public function __construct(string $value)
{
$this->value = $value;
}
public function value(): string
{
return $this->value;
}
}
// Example of usage:
$immutable = Immutable::withValue("my value");
$immutable->value(); // You can get its value but there is no way to modify it.
I need to serialize a proxy class. The class uses __set and __get to store values in an array. I want the serialization to look like it is just a flat object. In other words, my class looks like:
class Proxy
{
public $data = array();
public function __get($name)
{
return $data[$name]
}
}
and I want a foreach loop to return all the keys and values in $data, when I say:
foreach($myProxy as $key)
Is this possible?
class Proxy implements IteratorAggregate
{
public $data = array();
public function __get($name)
{
return $data[$name];
}
public function getIterator()
{
$o = new ArrayObject($this->data);
return $o->getIterator();
}
}
$p = new Proxy();
$p->data = array(2, 4, 6);
foreach ($p as $v)
{
echo $v;
}
Output is: 246.
See Object Iteration in the PHP docs for more details.
You want to implement the SPL iterator interface
Something like this:
class Proxy implements Iterator
{
public $data = array();
public function __get($name)
{
return $data[$name]
}
function rewind()
{
reset($this->data);
$this->valid = true;
}
function current()
{
return current($this->data)
}
function key()
{
return key($this->data)
}
function next() {
next($this->data);
}
function valid()
{
return key($this->data) !== null;
}
}
I have the current basic structure for each domain object that I need to create:
class Model_Company extends LP_Model
{
protected static $_gatewayName = 'Model_Table_Company';
protected static $_gateway;
protected static $_class;
public static function init()
{
if(self::$_gateway == null)
{
self::$_gateway = new self::$_gatewayName();
self::$_class = get_class();
}
}
public static function get()
{
self::init();
$param = func_get_arg(0);
if($param instanceof Zend_Db_Table_Row_Abstract)
{
$row = $param;
}
elseif(is_numeric($param))
{
$row = self::$_gateway->find($param)->current();
}
return new self::$_class($row);
}
public static function getCollection()
{
self::init();
$param = func_get_arg(0);
if($param instanceof Zend_Db_Table_Rowset_Abstract)
{
$rowset = $param;
}
elseif(!$param)
{
$rowset = self::$_gateway->fetchAll();
}
$array = array ();
foreach ($rowset as $row)
{
$array[] = new self::$_class($row);
}
return $array;
}
}
I initially tried to refactor the static methods into the parent LP_Model class only to learn finally what "late static binding" means in the php world.
I'm just wondering if anyone has suggestions on how to refactor this code so that I don't have to redeclare the same three functions in every domain object that I create?
How about this:
<?php
abstract class Model_Abstract
{
protected $_gatewayName = null;
protected $_gateway = null;
protected function _init()
{
$this->_gateway = new $this->_gatewayName();
}
protected function __construct($row = null)
{
$this->_init();
if ($row) {
$this->_data = $row;
}
}
public static function getAbstract($class, $param)
{
$model = new $class();
if($param instanceof Zend_Db_Table_Row_Abstract)
{
$row = $param;
}
elseif(is_numeric($param))
{
$row = $model->_gateway->find($param)->current();
}
return new $class($row);
}
public static function getAbstractCollection($class, $param = null)
{
$model = new $class();
if($param instanceof Zend_Db_Table_Rowset_Abstract)
{
$rowset = $param;
}
elseif($param === null)
{
$rowset = $model->_gateway->fetchAll();
}
$array = array ();
foreach ($rowset as $row)
{
$array[] = new $class($row);
}
return $array;
}
abstract public static function get($param);
abstract public static function getCollection($param = null);
}
class Model_Company extends Model_Abstract
{
protected $_gatewayName = 'Model_Table_Company';
public static function get($param) {
return self::getAbstract(__CLASS__, $param);
}
public static function getCollection($param = null) {
return self::getAbstractCollection(__CLASS__, $param);
}
}
class Model_Table_Company extends Zend_Db_Table_Abstract
{
protected $_name = 'company';
}
$model = Model_Company::get(1);
print "Got an object of type ".get_class($model)."\n";
$models = Model_Company::getCollection();
print "Got ".count($models)." objects of type ".get_class($models[0])."\n";
?>
Unfortunately, to make the functions easy to call, you have to duplicate get() and getCollection() in each subclass. The other option is to call the function in the parent class:
$model = Model_Abstract::getAbstract('Model_Company', 1);
print "Got an object of type ".get_class($model)."\n";
$models = Model_Abstract::getAbstractCollection('Model_Company');
print "Got ".count($models)." objects of type ".get_class($models[0])."\n";
You can rename the base class and its function names if you want to go that route. But the point is that you must name the child class in one place or the other: either make a boilerplate function in the child class as in my first example, or else name the class in a string as in my second example.