To give some context; this process was working prior to adding in the Serializable interface. The TestAction2 is not keeping a reference to itself after unserializing; and I have tried adding the Serializable to the Action class and telling it to serialize the $parent but still no difference. It is correctly unserializing the $method field though.
Class Action serializes the reference in $parent field when the serialization is being done on the class that is referencing it (class B), but not when it references itself. Something with serializing happening on Class A or B but not the Action class itself.
class A implements Serializable {
private static $label;
public function serialize() {
return serialize(self::$label);
}
public function unserialize($serialized) {
self::$label = unserialize($serialized);
}
}
class B extends A {
private $actions;
public function serialize() {
return serialize([
'actions' => $this->actions,
'parent' => parent::serialize()]);
}
public function unserialize($serialized) {
$data = unserialize($serialized);
$this->actions = $data['actions'];
parent::unserialize($data['parent']);
}
public function addAction($anAction) {
$this->actions[] = $anAction;
}
public function process() {
$this->addAction(new TestAction1($this, 'test1')); // WORKS FINE
$this->addAction(new TestAction2($this, 'test2')); // WORKS NOT!
}
}
class Action {
private $parent;
private $method;
public function __construct($cParent, $sMethod) {
$this->parent = $cParent;
}
}
class TestAction1 extends Action {
public function __construct($cParent, $sMethod) {
parent::__construct($cParent, $sMethod);
}
}
class TestAction2 extends Action {
private $maybeNeedLater;
public function __construct($cParent, $sMethod) {
$this->maybeNeedLater = $cParent;
parent::__construct($this, $sMethod); // pass $this instead
}
}
$testThis = new B();
$testThis->process();
$serialized = serialize($testThis);
$testThis = unserialize($serialized);
The Action class TestAction2 will have a null $parent field in the $actions field of $testThis
Your two different Action classes a bit of a red herring, because they are not the problem. Test2 just happens to use a reference other than $testThis; and many other reference would be wrong as well.
The real problem is that the order of serialization in your A/B classes is different from the order of unserialization. During serialization PHP encodes references with r (or R) and a number (r and R are different in many ways but the difference would just distract from the counting issue). In your example $needThis can be referenced with r:1;, A::label with r:2;, the actions array with r:3;, and so on. The $this reference to the TestAction2 object is r:8; (you can see this when you echo $serialized.)
When you call unserialize inside your unserialize function, there is no A::label (yet). Because of that all numbers greater than 1 will be off by one. r:8; now points to whatever is in maybeNeedLater.
Now things get a little complicated, because PHP normally does not create such strings. In PHP5 it seems to create some kind of error that is printed as NULL. In PHP7 parent will actually be a reference to $needThis.
Luckily there are a few easy ways to solve this:
Option 1:
Use a different format to encode your parent class:
class A implements Serializable {
private static $label;
public function serialize() {
return json_encode(self::$label);
}
public function unserialize($serialized) {
self::$label = json_decode($serialized);
}
}
Option 2:
Use double serialization in your child class and make sure the order fits:
class B extends A {
private $actions;
public function serialize() {
return json_encode([
'actions' => serialize($this->actions),
'parent' => parent::serialize()]);
}
public function unserialize($serialized) {
$data = json_decode($serialized, true);
$this->actions = unserialize($data['actions']);
parent::unserialize($data['parent']);
}
...
Related
I am using PHPStorm.
As an example, I have a simple query class with all sorts of methods to take care of sorting, filtering, ect. It eventually returns an instance of the class passed in the constructor.
class AnimalQuery {
private $classString;
public function __construct(string $classString){
$this->classString = $classString;
}
public function where(string $key, string $val): AnimalQuery {
// Database things would happen here
return $this;
}
public function orderBy(string $val): AnimalQuery {
// Database things would happen here
return $this;
}
public function first() {
// Database things would happen here
return new $this->classString();
}
}
And a class that acts as a model, for example:
class Dog {
public static function query(): AnimalQuery {
return new AnimalQuery(static::class);
}
public function bark() {
echo 'Woof!';
}
}
It can be called like so:
$doggo = Dog::query()->where('name', 'Fido')->orderBy('species')->first();
$doggo->bark();
This does return an instance of Dog. However, PHPStorm of course does not understand that. Is there a way to clue PHPStorm in that the first method returns an instance of the class passed in the constructor of AnimalQuery?
In Typescript I might've used generics for this, but those do not exist in PHP.
I've been using __set magic method with protected properties to monitor changes so that my classes know if they have something to save. Is there any way to monitor an array type property for changes? I understand that normally you access the array via a reference and functions like array_push won't trigger the __set method, they'll use a reference to the array.
What I want is basically this:
class Skill{ public $Player, $Name, $Level;}
class Player {
protected $Name, /*Other properties*/, $Skills /*Array*/
}
I then do tracking on all of the properties in Player to tell me if the persistence needs updated. (Skill would also have this function, but this shows the basic example). Also, I want to force them to remain synchronized (it's a bidirectional relationship).
Is there any way to do this that allows it to behave like an array (don't want to go through making a class just to synchronize those if I don't have to).
You could extend ArrayObject and proxy append:
class Skills extends ArrayObject
{
public function append($value)
{
// track changes
parent::append($value);
}
}
You could look into something like runkit_function_redifine(), but is it really too cumbersome to make helper methods for what you want? e.g.
class Player
{
private $skills = array();
protected function addSkill($skill)
{
// Do something.
//
$this->skills[] = $skill;
}
}
Or even a wrapper for an array to make it cleaner:
class FancyArray
{
private $content = array();
public function add($value)
{
// Do something.
//
$this->content[] = $value;
}
public function remove($value){ /* blah */ }
public function getContent(){ return $this->content; }
}
class Player
{
protected $skills;
public function __construct()
{
$this->skills = new FancyArray();
$this->skills->add("Ninjitsu");
}
}
I have the following class tree:
class A /* Base class */
{
private/protected/public $state
}
class B extends A /* Auto generated class, not to be modified */
{
private $v
public function getV() { return $this->v; }
public function setV($val) { $this->v = $val; }
}
class C extends B { /* Custom code */ }
There is only one class A. There are multiple classes like class B, and all of those classes will have a subclass like C. Class B gets auto-generated and should not be modified.
I am storing objects of type(s) C in the session. What I want to do is to store some state information in every instance, just before PHP gets it serialised, and that will do something with it when it's unserialised. I want all this to be implemented in class A.
Considering, I need to use either __sleep() or Serializable interface. Using __sleep is out of the question, because of what the PHP manual says:
It is not possible for __sleep() to return names of private properties in parent classes. Doing this will result in an E_NOTICE level error. Instead you may use the Serializable interface.
Meaning that if I sleep an instance of class C, I'll loose the private variables declared in B. So I want to use Serializable, but for some reason, I simply can't get it to do what I want.
In essence, I would like the object to be serialised just as if I didn't implement any serialisation stuff myself, I just want to add information to $state right before it happens. I've tried covering all data with ReflectionObject->getProperties(), but I can't seem to find the right way to fetch and set the private values in class B to be serialised and unserialised.
How do I do this?
You can do this using the Reflection classes. You'll have to get the properties of the class itself and each of it's parent classes. Getting and setting the property values can be done using ReflectionProperty's getValue and setValue methods, combined with setAccessible to get access to private and protected properties. Combining those, I came up with the following code:
<?php
class A implements Serializable /* Base class */
{
protected $state;
public function serialize()
{
$this->state = "something";
return serialize($this->_getState());
}
public function unserialize($data)
{
$this->_setState(unserialize($data));
}
protected function _getState()
{
$reflClass = new ReflectionClass(get_class($this));
$values = array();
while ($reflClass != null)
{
foreach ($reflClass->getProperties() as $property)
{
if ($property->getDeclaringClass() == $reflClass)
{
$property->setAccessible(true);
$values[] = array($reflClass->getName(), $property->getName(), $property->getValue($this));
}
}
$reflClass = $reflClass->getParentClass();
}
return $values;
}
protected function _setState($values)
{
foreach ($values as $_)
{
list($className, $propertyName, $propertyValue) = $_;
$property = new ReflectionProperty($className, $propertyName);
$property->setAccessible(true);
$property->setValue($this, $propertyValue);
}
}
}
class B extends A /* Auto generated class, not to be modified */
{
private $v;
public function getV() { return $this->v; }
public function setV($val) { $this->v = $val; }
}
class C extends B { /* Custom code */ }
$instance = new C();
$instance->setV("value");
$s = serialize($instance);
$instance2 = unserialize($s);
var_dump($instance, $instance2);
Which seems to do what you want.
i have a model class which extends CComponent
class CompanyModel extends CComponent{
private $company_pk;
public function getCompany_pk()
{
return $this->company_pk;
}
public function setCompany_pk($value) {
$this->company_pk = $value;
}
}
I have a function which fills this modal
public function getCompanyList() {
$companyList=array();
$company_obj = new CompanyModel();
$sql = "SELECT company_pk,name FROM tbl_company WHERE status = ".Constants::ACTIVE_COMPANY;
$command=$this->connection->createCommand($sql);
$command->setFetchMode(PDO::FETCH_ASSOC);
$rows=$command->queryAll();
foreach ($rows as $row){
$company_obj->company_pk = $row['company_pk'];
array_push($companyList,$company_obj);
}
return $companyList;
}
and my controller
Class UserController extends CController {
public function actionGetCompanyList() {
$model = new UserAction();
$ret_val = $model->getCompanyList();
echo CJSON::encode((array)$ret_val[0]);
}
}
and the JSON i get is
{"\u0000CompanyModel\u0000company_pk":"2"}
How can i remove those garbage values
I asume you can't decode them later, if you would change $company_pk to public it should encode correctly. Problem is with your object cast to array which adds NULL byte to your private member $company_pk. In echo CJSON::encode((array)$ret_val[0]); as it's array of OBJECTS.
You can do nasty.
$json = '{"\u0000CompanyModel\u0000company_pk":"2"}';
$json = str_replace('\u0000', '', $json);
var_dump(json_decode($json));
Or you pass object to CJSON::encode i dont know much of YII but it should handle object, as per manual http://www.yiiframework.com/doc/api/1.1/CJSON.
Here is example to reproduce issue:
class test {
private $private = 1;
public $publiv = 2;
}
$obj = new test();
$array = (array) $obj;
$json = json_encode($array);
var_dump($json);
var_dump(json_decode($json));
EDIT:
From the manual http://www.php.net/manual/en/language.types.array.php:
If an object is converted to an array, the result is an array whose elements are the object's properties. The keys are the member variable names, with a few notable exceptions: integer properties are unaccessible; private variables have the class name prepended to the variable name; protected variables have a '*' prepended to the variable name. These prepended values have null bytes on either side.
I did quick look to CJSON::encode from YII and you can use object directly, but your object must be traversable, so you must implement Iterator interface. http://www.php.net/manual/en/class.iterator.php.
EDIT:2
Implementing interface could be tricky, there is another option to call get_object_vars from within object and in this case you will get array which will work.
class CompanyModel extends CComponent
{
private $company_pk;
public function getCompany_pk()
{
return $this->company_pk;
}
public function setCompany_pk($value)
{
$this->company_pk = $value;
}
public function export()
{
return get_object_vars($this);
}
}
And then:
$ret_val = $model->getCompanyList();
echo CJSON::encode($ret_val->export());
Problem why it does not work for you with normal object because YII uses get_object_vars internally and it can't access private properties when it's in different scope.
I found out that if i extend CModel instead of CComponent in the model class, i could achieve what exactly i wanted. While extending this class you will have to override one abstract method. Otherwise it will throw error. My new Model class look like this
class CompanyModel extends CModel{
private $company_pk;
public function attributeNames()
{
return array( 'company_pk' );
}
public function attributeLabels()
{
return array( 'company_pk' => 'company_pk Label' );
}
public function getCompany_pk()
{
return $this->company_pk;
}
public function setCompany_pk($value) {
$this->company_pk = $value;
}
}
Is there something like struct data type in PHP?
Can anyone give me example for struct data type to understand this better?
If there's no such data type, how can I get a data type that behaves like a struct?
Closest you'd get to a struct is an object with all members public.
class MyStruct {
public $foo;
public $bar;
}
$obj = new MyStruct();
$obj->foo = 'Hello';
$obj->bar = 'World';
I'd say looking at the PHP Class Documentation would be worth it.
If you need a one-off struct, use the StdObject as mentioned in alex's answer.
You can use an array
$something = array(
'key' => 'value',
'key2' => 'value2'
);
or with standard object.
$something = new StdClass();
$something->key = 'value';
$something->key2 = 'value2';
I recommend 2 things. First is associative array.
$person = Array();
$person['name'] = "Joe";
$person['age'] = 22;
Second is classes.
Detailed documentation here: http://php.net/manual/en/language.oop5.php
This is quite high up on Google so I figured I'd share my implementation of a pseudo-struct using PHP8 syntax. The toArray() method does depend on Illuminate\Support\Str to transform keys to snake case (useful for mass assignment against Laravel models) however just remove it if it doesn't fit your use-case.
The base class:
<?php
namespace App\Infrastructure\Structs;
use App\Infrastructure\Exceptions\CannotMutateStructException;
use App\Infrastructure\Exceptions\ClassPropertyNotFoundException;
use Illuminate\Support\Str;
use ReflectionClass;
abstract class Struct
{
/**
* #param string $name
* #param mixed $value
* #throws CannotMutateStructException
*/
public function __set(string $name, mixed $value): void
{
throw new CannotMutateStructException(
'Structs are immutable. If you need mutable data then use a class instead.'
);
}
public function all(): array
{
$reflector = new ReflectionClass(static::class);
$response = [];
foreach ($reflector->getProperties() as $property) {
$response[$property->name] = $this->{$property->name};
}
return $response;
}
public function toArray(bool $snakeCase = false): array
{
$all = self::all();
if ($snakeCase === false) {
return $all;
}
$snakeCaseAll = [];
foreach ($all as $key => $value) {
$snakeCaseAll[Str::snake($key)] = $value;
}
return $snakeCaseAll;
}
}
How to use:
<?php
namespace App\Infrastructure\Structs;
class Person extends Struct
{
public function __construct(
public string $name,
public int $age,
public int $heightInCentimetres,
) {}
}
How to interact with it:
>>> $t = new \App\Infrastructure\Structs\Person('Max', 26, 182);
>>> $t->age
=> 26
>>> $t->age = 40
App\Infrastructure\Exceptions\CannotMutateStructException with message 'Structs are immutable. If you need mutable data then use a class instead.'
>>> $t->toArray(true)
=> [
"name" => "Max",
"age" => 26,
"height_in_centimetres" => 182,
]
Hopefully this helps someone.
Edit: with PHP8.1 we now have readonly properties which can make this even more concise.
Edit: with PHP8.2 we now have an even more concise way of doing this. I'd highly recommend reading this article in its entirety for a good example: https://stitcher.io/blog/evolution-of-a-php-object
However the general jist is to write readonly class Foo {} which removes to need to add it against each property.
I cobbled together a 'dynamic' struct class today, had a look tonight and someone has written something similar with better handling of constructor parameters, it might be worth a look:
http://code.activestate.com/recipes/577160-php-struct-port/
One of the comments on this page mentions an interesting thing in PHP - apparently you're able to cast an array as an object, which lets you refer to array elements using the arrow notation, as you would with a Struct pointer in C. The comment's example was as follows:
$z = array('foo' => 1, 'bar' => true, 'baz' => array(1,2,3));
//accessing values as properties
$y = (object)$z;
echo $y->foo;
I haven't tried this myself yet, but it may be that you could get the desired notation by just casting - if that's all you're after. These are of course 'dynamic' data structures, just syntactic sugar for accessing key/value pairs in a hash.
If you're actually looking for something more statically typed, then ASpencer's answer is the droid you're looking for (as Obi-Wan might say.)
It seems that the struct datatype is commonly used in SOAP:
var_dump($client->__getTypes());
array(52) {
[0] =>
string(43) "struct Bank {\n string Code;\n string Name;\n}"
}
This is not a native PHP datatype!
It seems that the properties of the struct type referred to in SOAP can be accessed as a simple PHP stdClass object:
$some_struct = $client->SomeMethod();
echo 'Name: ' . $some_struct->Name;
Only associative arrays are structs in PHP.
And you can't make them strict on their own.
But you can sort of fake structure strictness with classes and interfaces, but beware that unlike structures, class instances are not passed in arguments, their identifiers are!
You can define a struct through an interface (or at least close to it)
Structs enforce a certain structure on an object.
PHP (<= 7.3) does not have native structs, but you can get around it with interfaces and type hinting:
interface FooStruct
{
public function name() : string;
}
interface BarStruct
{
public function id() : int;
}
interface MyStruct
{
public function foo() : FooStruct;
public function bar() : BarStruct;
}
Any class implementing MyStruct will be a MyStruct.
The way it's build up is not up to the struct, it just ensures that the data returned is correct.
What about setting data?
Setting struct data is problematic as we end up with getters and setters and it's something that is close to an anemic object or a DTO and is considered an anti-pattern by some people
Wrong example:
interface FooStruct
{
public function getName() : string;
public function setName(string $value) : FooStruct;
}
interface BarStruct
{
public function getId() : int;
public function setId(int $value) : BarStruct;
}
interface MyStruct
{
public function getFoo() : FooStruct;
public function setFoo(FooStruct $value) : MyStruct;
public function getBar() : BarStruct;
public function setBar(BarStruct $value) : MyStruct;
}
Then we end up with class implementations that might be mutable, and a struct must not mutate, this is to make it a "data type", just like int, string.
Yet there's no way to restrict that with interfaces in PHP, meaning people will be able to implement your struct interface in a class that is not a struct.
Make sure to keep the instance immutable
Also a struct may then be instantiated without the correct data and trigger errors when trying to access the data.
An easy and reliable way to set data in a PHP struct class is through its constructor
interface FooStruct
{
public function name() : string;
}
interface BarStruct
{
public function id() : int;
}
interface MyStruct
{
public function foo() : FooStruct;
public function bar() : BarStruct;
}
class Foo implements FooStruct
{
protected $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function name() : string
{
return $this->name;
}
}
class Bar implements BarStruct
{
protected $id;
public function __construct(string $id)
{
$this->id = $id;
}
public function id() : int
{
return $this->id;
}
}
class My implements MyStruct
{
protected $foo, $bar;
public function __construct(FooStruct $foo, BarStruct $bar)
{
$this->foo = $foo;
$this->bar = $bar;
}
public function foo() : FooStruct
{
return $this->foo;
}
public function bar() : BarStruct
{
return $this->bar;
}
}
Type hinting using interfaces: (if your IDE supports it)
If you don't mind not having the strict type checking, then another way would be using interfaces or classes with comments for the IDE.
/**
* Interface My
* #property Foo $foo
* #property Bar $bar
*/
interface My
{
}
/**
* Interface Foo
* #property string|integer $id
* #property string $name
*/
interface Foo
{
}
/**
* Interface Bar
* #property integer $id
*/
interface Bar
{
}
The reason to use interfaces instead of classes is for the same reason why interfaces exist in the first place, because then many classes with many implementations can have this same structure and each method/function that uses it will support every class with this interface.
This depends on your IDE, so you might need to use classes instead or just live without it.
Note:
Remember that you have to validate/sanitize the data in the instance elsewhere in the code to match the comment.
A public class is one option, if you want something more encapsulated you can use an abstract/anonymous class combination. My favorite part is that autocomplete still works (for PhpStorm) for this but I don't have a public class sitting around.
<?php
final class MyParentClass
{
/**
* #return MyStruct[]
*/
public function getData(): array
{
return array(
$this->createMyObject("One", 1.0, new DateTime("now")),
$this->createMyObject("Two", 2.0, new DateTime("tommorow"))
);
}
private function createMyObject(string $description, float $magnitude, DateTime $timeStamp): MyStruct
{
return new class(func_get_args()) extends MyStruct {
protected function __construct(array $args)
{
$this->description = $args[0];
$this->magnitude = $args[1];
$this->timeStamp = $args[2];
}
};
}
}
abstract class MyStruct
{
public string $description;
public float $magnitude;
public DateTime $timeStamp;
}