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;
}
}
Related
I need to be able to set my object like this:
$obj->foo = 'bar';
then I need to use it as an array like that:
if($obj['foo'] == 'bar'){
//more code here
}
Just add implements ArrayAccess to your class and add the required methods:
public function offsetExists($offset)
public function offsetGet($offset)
public function offsetSet($offset, $value)
public function offsetUnset($offset)
See http://php.net/manual/en/class.arrayaccess.php
Want to improve this post? Provide detailed answers to this question, including citations and an explanation of why your answer is correct. Answers without enough detail may be edited or deleted.
Try extending ArrayObject
You'll also need to implement a __get Magic Method as Valentin Golev mentioned.
Your class will need to looks something like this:
Class myClass extends ArrayObject {
// class property definitions...
public function __construct()
{
//Do Stuff
}
public function __get($n) { return $this[$n]; }
// Other methods
}
ArrayObject implements the ArrayAccess interface (and some more). Using the ARRAY_AS_PROPS flag it provides the functionality you're looking for.
$obj = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS);
$obj->foo = 'bar';
echo $obj['foo'];
Alternatively you can implement the ArrayAccess interface in one of your own classes:
class Foo implements ArrayAccess {
public function offsetExists($offset) {
return isset($this->$offset);
}
public function offsetGet($offset) {
return $this->$offset;
}
public function offsetSet($offset , $value) {
$this->$offset = $value;
}
public function offsetUnset($offset) {
unset($this->$offset);
}
}
$obj = new Foo;
$obj->foo = 'bar';
echo $obj['foo'];
You can access PHP object as PHP array, but in different ways. Try this:
$obj->{'foo'}
That is similar with accessing array like this:
$arr['foo']
You can also do this:
$propertyName = 'foo';
$obj->$propertyName; // same like first example
You'll have to implement the ArrayAccess interface to be able to do that -- which only means implementing a few (4 to be exact) simple methods :
ArrayAccess::offsetExists : Whether or not an offset exists.
ArrayAccess::offsetGet : Returns the value at specified offset.
ArrayAccess::offsetSet : Assigns a value to the specified offset.
and ArrayAccess::offsetUnset : Unsets an offset.
There is a full example on the manual's page I pointed to ;-)
You're mixing objects and arrays. You can create and access an object like so:
$obj = new stdClass;
$obj->foo = 'bar';
if($obj->foo == 'bar'){
// true
}
and an array like so:
$obj = new Array();
$obj['foo'] = 'bar';
if($obj['foo'] == 'bar'){
// true
}
You can define a class and add implements ArrayAccess if you want to access your class as both an array and a class.
http://www.php.net/manual/en/language.oop5.php
Your object must implement the ArrayAccess interface, then PHP will allow you to use the square brackets like that.
You could also cast the object as an array:
if((array)$obj['foo'] == 'bar'){
//more code here
}
Enhance Class capability with no functionality drawbacks
You can also use ArrayAccess to access a single array property in your class and leave other properties being accessed in OOP way. Yet still it will work as you requested.
class Foo implements \ArrayAccess
{
/**
* mixed[] now you can access this array using your object
* like a normal array Foo['something'] = 'blablabla'; echo Foo['something']; ... and so on
* other properties will remain accessed as normal: $Foo->getName();
*/
private myArrayOptions = [];
private $name = 'lala';
...
public function offsetExists($offset)
{
return isset($this->myArrayOptions[$offset]);
}
public function offsetGet($offset)
{
if ($this->offsetExists($offset)) {
return $this->myArrayOptions[$offset];
}
return null; // or throw the exception;
}
public function offsetSet($offset, $value)
{
$this->myArrayOptions[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->myArrayOptions[$offset]);
}
public function getName()
{
return $this->name;
}
public function __set($offset, $value){
$this->myArrayOptions[$offset] = $value;
}
...
}
The above will work as you expected.
$obj->foo = 'bar';
if($obj['foo'] == 'bar'){
echo "WoWo";
}
Also note that Foo['name'] !== Foo->getName()
those a two different variables
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']);
}
...
I have a property that stores a class name as a string. I then want to use this to call a static method of said class. As far as I know, this is possible since PHP 5.3. I am running 5.6.x on a vagrant box.
I want to do this:
$item = $this->className::getItem($id);
But I get the following error:
Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM)...
The following works fine:
$c = $this->className;
$item = $c::getItem($id);
Any idea why? Is this not the same thing?
The problem is that you are access are property from a class in the first useage, but then in the second try you are parsing the value of the class property (into $c), what is a classname as string, and this can used for static calls to static class functions. The first try, trys to access the static method on an string (the class property).
class a {
static function b(){echo'works';}
}
$a='a';
$a::b();
But the real issue of the error is, that this ->FooBar:: is an syntax error in PHP.
JOUM is completely right!
Based on his answer I wrote a class like a fabric.
Interface GetItem
{
public static function getItem($id);
}
Abstract Class Item
{
private $id;
function __construct($id)
{
$this->id = $id;
}
}
Class ItemA extends Item implements GetItem
{
public static function getItem($id)
{
$item = new ItemA($id);
return $item;
}
}
Class ItemB extends Item implements GetItem
{
public static function getItem($id)
{
$item = new ItemB($id);
return $item;
}
}
Class Fabric
{
function fabricItem($classname,$id)
{
$item = $classname::getItem($id);
return $item;
}
}
$fabric = new Fabric();
$a = $fabric->fabricItem("ItemA",3);
$b = $fabric->fabricItem("ItemB",4);
var_dump($fabric);
var_dump($a);
var_dump($b);
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 PHP Class which requires a unique value in its constructor. If multiple instances of the same class are passed the same value the results are horrific.
How would I go about detecting other instances of a Class so I can check and prevent this from happening before constructing any new ones?
A simple solution would be to keep a static array of the values inside the class. Then, when a new instance is created, check the static array's contents in the constructor.
Something like..
class Foo {
private static $usedValues = array();
public function __construct($val) {
if(in_array($val, self::$usedValues)) {
throw new Exception('blah');
}
self::$usedValues[] = $val;
}
}
I think the multiton pattern is right for you.
class Foo {
static protected $_instances = array();
static public function getInstance($id) {
if(!self::exists($id)) {
self::$_instances[$id] = new Foo($id);
}
return self::$_instances[$id];
}
static public function exists($id) {
return isset(self::$_instances[$id]);
}
protected function __construct($id) {
}
}